# HG changeset patch # User Mads Kiilerich # Date 1482536078 -3600 # Node ID d89d586b26ae2ffe1127af1cb5d174adbccbfefa # Parent dc94e662ee74dc16d14327621286adc1a2459039# Parent b4dd4c16c12d8932d99a2182c7e8ed0f456fb85f Merge stable diff -r b4dd4c16c12d -r d89d586b26ae .hgignore --- a/.hgignore Wed Dec 21 14:56:09 2016 +0000 +++ b/.hgignore Sat Dec 24 00:34:38 2016 +0100 @@ -16,7 +16,6 @@ ^docs/build/ ^docs/_build/ ^data$ -^kallithea/tests/data$ ^sql_dumps/ ^\.settings$ ^\.project$ @@ -29,3 +28,4 @@ ^fabfile.py ^\.idea$ ^\.cache$ +/__pycache__$ diff -r b4dd4c16c12d -r d89d586b26ae .travis.yml --- a/.travis.yml Wed Dec 21 14:56:09 2016 +0000 +++ b/.travis.yml Sat Dec 24 00:34:38 2016 +0100 @@ -3,7 +3,7 @@ - "2.6" - "2.7" -env: +env: - TEST_DB=sqlite:////tmp/kallithea_test.sqlite - TEST_DB=mysql://root@127.0.0.1/kallithea_test - TEST_DB=postgresql://postgres@127.0.0.1/kallithea_test diff -r b4dd4c16c12d -r d89d586b26ae CONTRIBUTORS --- a/CONTRIBUTORS Wed Dec 21 14:56:09 2016 +0000 +++ b/CONTRIBUTORS Sat Dec 24 00:34:38 2016 +0100 @@ -4,16 +4,26 @@ Takumi IINO 2012-2016 Unity Technologies 2012-2016 Andrew Shadura 2012 2014-2016 - Dominik Ruf 2012 2014 2016 + Dominik Ruf 2012 2014-2016 + Thomas De Schampheleire 2014-2016 + Étienne Gilli 2015-2016 + Jan Heylen 2015-2016 + Robert Martinez 2015-2016 + Robert Rauch 2015-2016 Søren Løvborg 2015-2016 + Angel Ezquerra 2016 + Asterios Dimitriou 2016 + Kateryna Musina 2016 Konstantin Veretennicov 2016 + Oscar Curero 2016 Robert James Dennington 2016 + timeless@gmail.com 2016 + YFdyh000 2016 Aras Pranckevičius 2012-2013 2015 Sean Farley 2013-2015 Christian Oyarzun 2014-2015 Joseph Rivera 2014-2015 Michal Čihař 2014-2015 - Thomas De Schampheleire 2014-2015 Anatoly Bubenkov 2015 Andrew Bartlett 2015 Balázs Úr 2015 @@ -24,9 +34,8 @@ Denis Blanchette 2015 duanhongyi 2015 EriCSN Chang 2015 - Étienne Gilli 2015 Grzegorz Krason 2015 - Jan Heylen 2015 + Jiří Suchan 2015 Kazunari Kobayashi 2015 Kevin Bullock 2015 kobanari 2015 @@ -39,12 +48,11 @@ Nick High 2015 Niemand Jedermann 2015 Peter Vitt 2015 - Robert Martinez 2015 - Robert Rauch 2015 Ronny Pfannschmidt 2015 Sam Jaques 2015 Tuux 2015 Viktar Palstsiuk 2015 + Ante Ilic 2014 Bradley M. Kuhn 2014 Calinou 2014 Daniel Anderson 2014 diff -r b4dd4c16c12d -r d89d586b26ae LICENSE.md --- a/LICENSE.md Wed Dec 21 14:56:09 2016 +0000 +++ b/LICENSE.md Sat Dec 24 00:34:38 2016 +0100 @@ -22,20 +22,33 @@ of Kallithea. +Alembic +------- + +Kallithea incorporates an [Alembic](http://alembic.zzzcomputing.com/en/latest/) +"migration environment" in `kallithea/alembic`, portions of which is: + +Copyright © 2009-2016 by Michael Bayer. +Alembic is a trademark of Michael Bayer. + +and licensed under the MIT-permissive license, which is +[included in this distribution](MIT-Permissive-License.txt). + Bootstrap --------- -Kallithea incorporates parts of the Javascript system called +Kallithea incorporates the web framework called [Bootstrap](http://getbootstrap.com/), which is: -Copyright © 2012 Twitter, Inc. +Copyright © 2011-2016 Twitter, Inc. +Copyright © 2011-2016 The Bootstrap Authors -and licensed under -[the Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). - -A copy of the Apache License 2.0 is also included in this distribution in its -entirety in the file Apache-License-2.0.txt +and licensed under the MIT-permissive license, which is +[included in this distribution](MIT-Permissive-License.txt), +which can be found together with its Corresponding Source in +https://github.com/twbs/bootstrap at tag v3.3.7 (mirrored at +https://kallithea-scm.org/repos/mirror/bootstrap/ ). @@ -62,29 +75,27 @@ ------ Kallithea incorporates the Javascript system called -[jQuery](http://jquery.org/), -[herein](kallithea/public/js/jquery-1.11.1.min.js), and the Corresponding -Source can be found in https://github.com/jquery/jquery at tag 1.11.1 -(mirrored at https://kallithea-scm.org/repos/mirror/jquery/files/1.11.1/ ). +[jQuery](http://jquery.org/), [herein](kallithea/public/js/jquery.min.js), +which can be found together with its Corresponding Source in +https://github.com/jquery/jquery at tag 1.12.3 (mirrored at +https://kallithea-scm.org/repos/mirror/jquery/files/1.12.3/ ). It is Copyright 2013 jQuery Foundation and other contributors http://jquery.com/ and is under an [MIT-permissive license](MIT-Permissive-License.txt). -Mousetrap ---------- - -Kallithea incorporates parts of the Javascript system called -[Mousetrap](http://craig.is/killing/mice/), which is: +DataTables +---------- - Copyright 2013 Craig Campbell +Kallithea incorporates the Javascript system called +[DataTables](http://www.datatables.net/) (jquery.dataTables.min.js and .css) +which can be found together with their Corresponding Source in +https://github.com/DataTables/DataTables at tag 1.10.11 (mirrored at +https://kallithea-scm.org/repos/mirror/DataTables/files/1.10.11/ ). -and licensed under -[the Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). - -A [copy of the Apache License 2.0](Apache-License-2.0.txt) is also included -in this distribution. +It is Copyright 2008-2015 SpryMedia Ltd. and is under an +[MIT-permissive license](MIT-Permissive-License.txt). @@ -213,9 +224,9 @@ git checkout hudson-yui2-2800 ln -sf JumpToPageDropDown.js src/paginator/js/JumpToPageDropdown.js # work around inconsistent casing rm -f tmp.js - for m in yahoo event dom connection animation dragdrop element datasource autocomplete container event-delegate json datatable paginator; do - rm -f build/\$m/\$m.js - ( cd src/\$m && ant build deploybuild ) && sed -e 's,@VERSION@,2.9.0,g' -e 's,@BUILD@,2800,g' build/\$m/\$m.js >> tmp.js + for m in yahoo event dom animation datasource autocomplete event-delegate; do + rm -f build/$m/$m.js + ( cd src/$m && ant build deploybuild ) && sed -e 's,@VERSION@,2.9.0,g' -e 's,@BUILD@,2800,g' build/$m/$m.js >> tmp.js done java -jar ../builder/componentbuild/lib/yuicompressor/yuicompressor-2.4.4.jar tmp.js -o yui.2.9.js @@ -241,19 +252,6 @@ -Migrate -------- - -Kallithea incorporates in kallithea/lib/dbmigrate/migrate parts of the Python -system called [Migrate or sqlalchemy-migrate](https://github.com/stackforge/sqlalchemy-migrate), -which is: - -Copyright (c) 2009 Evan Rosson, Jan Dittberner, Domen Kožar - -and licensed under the MIT-permissive license, which is -[included in this distribution](MIT-Permissive-License.txt). - - Icon fonts ---------- diff -r b4dd4c16c12d -r d89d586b26ae MANIFEST.in --- a/MANIFEST.in Wed Dec 21 14:56:09 2016 +0000 +++ b/MANIFEST.in Sat Dec 24 00:34:38 2016 +0100 @@ -8,11 +8,11 @@ include development.ini recursive-include docs * recursive-include init.d * +recursive-include kallithea/alembic * include kallithea/bin/ldap_sync.conf include kallithea/bin/template.ini.mako include kallithea/config/deployment.ini_tmpl recursive-include kallithea/i18n * -recursive-include kallithea/lib/dbmigrate *.py_tmpl README migrate.cfg recursive-include kallithea/public * recursive-include kallithea/templates * recursive-include kallithea/tests/fixtures * diff -r b4dd4c16c12d -r d89d586b26ae README.rst --- a/README.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/README.rst Sat Dec 24 00:34:38 2016 +0100 @@ -162,76 +162,14 @@ install it via the command: ``pip install sphinx`` . -Converting from RhodeCode -------------------------- - -Currently, you have two options for working with an existing RhodeCode -database: - -- keep the database unconverted (intended for testing and evaluation) -- convert the database in a one-time step - -Maintaining interoperability -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Interoperability with RhodeCode 2.2.X installations is provided so you don't -have to immediately commit to switching to Kallithea. This option will most -likely go away once the two projects have diverged significantly. - -To run Kallithea on a RhodeCode database, run:: - - echo "BRAND = 'rhodecode'" > kallithea/brand.py - -This location will depend on where you installed Kallithea. If you installed -via:: - - python2 setup.py install - -then you will find this location at -``$VIRTUAL_ENV/lib/python2.7/site-packages/Kallithea-0.1-py2.7.egg/kallithea``. - -One-time conversion -~~~~~~~~~~~~~~~~~~~ +Migrating from RhodeCode +------------------------ -Alternatively, if you would like to convert the database for good, you can use -a helper script provided by Kallithea. This script will operate directly on the -database, using the database string you can find in your ``production.ini`` (or -``development.ini``) file. For example, if using SQLite:: - - cd /path/to/kallithea - cp /path/to/rhodecode/rhodecode.db kallithea.db - pip install sqlalchemy-migrate - python2 kallithea/bin/rebranddb.py sqlite:///kallithea.db - -.. Note:: - - If you started out using the branding interoperability approach mentioned - above, watch out for stray brand.pyc after removing brand.py. - -Git hooks -~~~~~~~~~ - -After switching to Kallithea, it will be necessary to update the Git_ hooks in -your repositories. If not, the Git_ hooks from RhodeCode will still be called, -which will cause ``git push`` to fail every time. - -If you do not have any custom Git_ hooks deployed, perform the following steps -(this may take some time depending on the number and size of repositories you -have): - -1. Log-in as an administrator. - -2. Open page *Admin > Settings > Remap and Rescan*. - -3. Turn on the option **Install Git Hooks**. - -4. Turn on the option **Overwrite existing Git hooks**. - -5. Click on the button **Rescan Repositories**. - -If you do have custom hooks, you will need to merge those changes manually. In -order to get sample hooks from Kallithea, the easiest way is to create a new Git_ -repository, and have a look at the hooks deployed there. +Kallithea 0.3.2 and earlier supports migrating from an existing RhodeCode +installation. To migrate, install Kallithea 0.3.2 and follow the +instructions in the 0.3.2 README to perform a one-time conversion of the +database from RhodeCode to Kallithea, before upgrading to this version +of Kallithea. .. _virtualenv: http://pypi.python.org/pypi/virtualenv diff -r b4dd4c16c12d -r d89d586b26ae dev_requirements.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dev_requirements.txt Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,7 @@ +pytest>=2.7.0,<4.0 +pytest-runner +pytest-sugar>=0.7.0 +pytest-catchlog +WebTest<2 # this is also a Pylons dependency and pinned in setup.py to avoid version conflicts for WebOb +mock +sphinx diff -r b4dd4c16c12d -r d89d586b26ae development.ini --- a/development.ini Wed Dec 21 14:56:09 2016 +0000 +++ b/development.ini Sat Dec 24 00:34:38 2016 +0100 @@ -55,32 +55,31 @@ #error_email_from = paste_error@example.com ## SMTP server settings -## Only smtp_server is mandatory. All other settings take the specified default -## values. +## If specifying credentials, make sure to use secure connections. +## Default: Send unencrypted unauthenticated mails to the specified smtp_server. +## For "SSL", use smtp_use_ssl = true and smtp_port = 465. +## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587. #smtp_server = smtp.example.com #smtp_username = #smtp_password = #smtp_port = 25 +#smtp_use_ssl = false #smtp_use_tls = false -#smtp_use_ssl = false -## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.). -## If empty, use any of the authentication parameters supported by the server. -#smtp_auth = [server:main] ## PASTE ## #use = egg:Paste#http ## nr of worker threads to spawn -#threadpool_workers = 5 +#threadpool_workers = 1 ## max request before thread respawn -#threadpool_max_requests = 10 +#threadpool_max_requests = 100 ## option to use threads of process #use_threadpool = true ## WAITRESS ## use = egg:waitress#main ## number of worker threads -threads = 5 +threads = 1 ## MAX BODY SIZE 100GB max_request_body_size = 107374182400 ## use poll instead of select, fixes fd limits, may not work on old @@ -98,7 +97,7 @@ ## recommended for bigger setup is using of of other than sync one #worker_class = sync #max_requests = 1000 -## ammount of time a worker can handle request before it gets killed and +## amount of time a worker can handle request before it gets killed and ## restarted #timeout = 3600 @@ -163,6 +162,7 @@ #cheaper-step = 1 ## COMMON ## +#host = 127.0.0.1 host = 0.0.0.0 port = 5000 @@ -198,9 +198,6 @@ ## cut off limit for large diffs (size in bytes) cut_off_limit = 256000 -## use cache version of scm repo everywhere -vcs_full_cache = true - ## force https in Kallithea, fixes https redirects, assumes it's always https force_https = false @@ -226,6 +223,11 @@ show_sha_length = 12 show_revision_number = false +## Canonical URL to use when creating full URLs in UI and texts. +## Useful when the site is available under different names or protocols. +## Defaults to what is provided in the WSGI environment. +#canonical_url = https://kallithea.example.com/repos + ## gist URL alias, used to create nicer urls for gist. This should be an ## url that does rewrites to _admin/gists/. ## example: http://gist.example.com/{gistid}. Empty means use the internal @@ -245,7 +247,7 @@ # FilesController:archivefile ## default encoding used to convert from and to unicode -## can be also a comma seperated list of encoding in case of mixed encodings +## can be also a comma separated list of encoding in case of mixed encodings default_encoding = utf8 ## issue tracker for Kallithea (leave blank to disable, absent for default) @@ -280,12 +282,6 @@ #issue_server_link_wiki = https://wiki.example.com/{id} #issue_prefix_wiki = WIKI- -## instance-id prefix -## a prefix key for this instance used for cache invalidation when running -## multiple instances of kallithea, make sure it's globally unique for -## all running kallithea instances. Leave empty if you don't use it -instance_id = - ## alternative return HTTP header for failed authentication. Default HTTP ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with ## handling that. Set this variable to 403 to return HTTPForbidden @@ -301,19 +297,29 @@ ## allows to setup custom hooks in settings page allow_custom_hooks_settings = True +## extra extensions for indexing, space separated and without the leading '.'. +# index.extensions = +# gemfile +# lock + +## extra filenames for indexing, space separated +# index.filenames = +# .dockerignore +# .editorconfig +# INSTALL +# CHANGELOG + #################################### ### CELERY CONFIG #### #################################### use_celery = false -broker.host = localhost -broker.vhost = rabbitmqhost -broker.port = 5672 -broker.user = rabbitmq -broker.password = qweqwe + +## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq: +broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost celery.imports = kallithea.lib.celerylib.tasks - +celery.accept.content = pickle celery.result.backend = amqp celery.result.dburi = amqp:// celery.result.serialier = json @@ -322,11 +328,9 @@ #celery.amqp.task.result.expires = 18000 celeryd.concurrency = 2 -#celeryd.log.file = celeryd.log -celeryd.log.level = DEBUG celeryd.max.tasks.per.child = 1 -## tasks will never be sent to the queue, but executed locally instead. +## If true, tasks will never be sent to the queue, but executed locally instead. celery.always.eager = false #################################### @@ -422,7 +426,7 @@ ## (saves API quota for intensive logging) appenlight.logging_on_error = false -## list of additonal keywords that should be grabbed from environ object +## list of additional keywords that should be grabbed from environ object ## can be string with comma separated list of words in lowercase ## (by default client will always send following info: ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that @@ -480,19 +484,25 @@ ######################################################### # SQLITE [default] -sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60 +sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60 # POSTGRESQL -#sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea +#sqlalchemy.url = postgresql://user:pass@localhost/kallithea # MySQL -#sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea +#sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8 # see sqlalchemy docs for others -sqlalchemy.db1.echo = false -sqlalchemy.db1.pool_recycle = 3600 -sqlalchemy.db1.convert_unicode = true +sqlalchemy.echo = false +sqlalchemy.pool_recycle = 3600 + +################################ +### ALEMBIC CONFIGURATION #### +################################ + +[alembic] +script_location = kallithea:alembic ################################ ### LOGGING CONFIGURATION #### @@ -560,16 +570,16 @@ class = StreamHandler args = (sys.stderr,) #level = INFO +level = DEBUG #formatter = generic -level = DEBUG formatter = color_formatter [handler_console_sql] class = StreamHandler args = (sys.stderr,) #level = WARN +level = DEBUG #formatter = generic -level = DEBUG formatter = color_formatter_sql ################ diff -r b4dd4c16c12d -r d89d586b26ae docs/api/api.rst --- a/docs/api/api.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/api/api.rst Sat Dec 24 00:34:38 2016 +0100 @@ -9,34 +9,8 @@ ``/_admin/api``. -API access for web views -++++++++++++++++++++++++ - -API access can also be turned on for each web view in Kallithea that is -decorated with the ``@LoginRequired`` decorator. Some views use -``@LoginRequired(api_access=True)`` and are always available. By default only -RSS/Atom feed views are enabled. Other views are -only available if they have been whitelisted. Edit the -``api_access_controllers_whitelist`` option in your .ini file and define views -that should have API access enabled. - -For example, to enable API access to patch/diff, raw file and archive:: - - api_access_controllers_whitelist = - ChangesetController:changeset_patch, - ChangesetController:changeset_raw, - FilesController:raw, - FilesController:archivefile - -After this change, a Kallithea view can be accessed without login by adding a -GET parameter ``?api_key=`` to the URL. - -Exposing raw diffs is a good way to integrate with -third-party services like code review, or build farms that can download archives. - - API access -++++++++++ +---------- Clients must send JSON encoded JSON-RPC requests:: @@ -76,7 +50,7 @@ API client -++++++++++ +---------- Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient way to call the JSON-RPC API. @@ -110,11 +84,11 @@ API methods -+++++++++++ +----------- pull ----- +^^^^ Pull the given repo from remote location. Can be used to automatically keep remote repos up to date. @@ -136,7 +110,7 @@ error : null rescan_repos ------------- +^^^^^^^^^^^^ Rescan repositories. If ``remove_obsolete`` is set, Kallithea will delete repos that are in the database but not in the filesystem. @@ -159,7 +133,7 @@ error : null invalidate_cache ----------------- +^^^^^^^^^^^^^^^^ Invalidate the cache for a repository. This command can only be executed using the api_key of a user with admin rights, @@ -181,7 +155,7 @@ error : null lock ----- +^^^^ Set the locking state on the given repository by the given user. If the param ``userid`` is skipped, it is set to the ID of the user who is calling this method. @@ -212,7 +186,7 @@ error : null get_ip ------- +^^^^^^ Return IP address as seen from Kallithea server, together with all defined IP addresses for given user. @@ -244,12 +218,12 @@ error : null get_user --------- +^^^^^^^^ Get a user by username or userid. The result is empty if user can't be found. If userid param is skipped, it is set to id of user who is calling this method. Any userid can be specified when the command is executed using the api_key of a user with admin rights. -Regular users can only speicy their own userid. +Regular users can only specify their own userid. INPUT:: @@ -288,7 +262,7 @@ error: null get_users ---------- +^^^^^^^^^ List all existing users. This command can only be executed using the api_key of a user with admin rights. @@ -325,7 +299,7 @@ .. _create-user: create_user ------------ +^^^^^^^^^^^ Create new user. This command can only be executed using the api_key of a user with admin rights. @@ -371,7 +345,7 @@ kallithea-api create_user username:bent email:bent@example.com firstname:Bent lastname:Bentsen extern_type:ldap extern_name:uid=bent,dc=example,dc=com update_user ------------ +^^^^^^^^^^^ Update the given user if such user exists. This command can only be executed using the api_key of a user with admin rights. @@ -415,7 +389,7 @@ error: null delete_user ------------ +^^^^^^^^^^^ Delete the given user if such a user exists. This command can only be executed using the api_key of a user with admin rights. @@ -439,7 +413,7 @@ error: null get_user_group --------------- +^^^^^^^^^^^^^^ Get an existing user group. This command can only be executed using the api_key of a user with admin rights. @@ -481,7 +455,7 @@ error : null get_user_groups ---------------- +^^^^^^^^^^^^^^^ List all existing user groups. This command can only be executed using the api_key of a user with admin rights. @@ -507,7 +481,7 @@ error : null create_user_group ------------------ +^^^^^^^^^^^^^^^^^ Create a new user group. This command can only be executed using the api_key of a user with admin rights. @@ -537,7 +511,7 @@ error: null add_user_to_user_group ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ Adds a user to a user group. If the user already is in that group, success will be ``false``. @@ -564,7 +538,7 @@ error: null remove_user_from_user_group ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ Remove a user from a user group. If the user isn't in the given group, success will be ``false``. @@ -591,7 +565,7 @@ error: null get_repo --------- +^^^^^^^^ Get an existing repository by its name or repository_id. Members will contain either users_group or users associated to that repository. @@ -680,7 +654,7 @@ error: null get_repos ---------- +^^^^^^^^^ List all existing repositories. This command can only be executed using the api_key of a user with admin rights, @@ -717,7 +691,7 @@ error: null get_repo_nodes --------------- +^^^^^^^^^^^^^^ Return a list of files and directories for a given path at the given revision. It is possible to specify ret_type to show only ``files`` or ``dirs``. @@ -748,7 +722,7 @@ error: null create_repo ------------ +^^^^^^^^^^^ Create a repository. If the repository name contains "/", all needed repository groups will be created. For example "foo/bar/baz" will create repository groups @@ -800,7 +774,7 @@ error: null update_repo ------------ +^^^^^^^^^^^ Update a repository. This command can only be executed using the api_key of a user with admin rights, @@ -860,7 +834,7 @@ error: null fork_repo ---------- +^^^^^^^^^ Create a fork of the given repo. If using Celery, this will return success message immediately and a fork will be created @@ -896,7 +870,7 @@ error: null delete_repo ------------ +^^^^^^^^^^^ Delete a repository. This command can only be executed using the api_key of a user with admin rights, @@ -923,7 +897,7 @@ error: null grant_user_permission ---------------------- +^^^^^^^^^^^^^^^^^^^^^ Grant permission for a user on the given repository, or update the existing one if found. This command can only be executed using the api_key of a user with admin rights. @@ -949,7 +923,7 @@ error: null revoke_user_permission ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ Revoke permission for a user on the given repository. This command can only be executed using the api_key of a user with admin rights. @@ -974,7 +948,7 @@ error: null grant_user_group_permission ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ Grant permission for a user group on the given repository, or update the existing one if found. @@ -1001,7 +975,7 @@ error: null revoke_user_group_permission ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Revoke permission for a user group on the given repository. This command can only be executed using the api_key of a user with admin rights. @@ -1024,3 +998,29 @@ "success": true } error: null + + +API access for web views +------------------------ + +API access can also be turned on for each web view in Kallithea that is +decorated with the ``@LoginRequired`` decorator. Some views use +``@LoginRequired(api_access=True)`` and are always available. By default only +RSS/Atom feed views are enabled. Other views are +only available if they have been whitelisted. Edit the +``api_access_controllers_whitelist`` option in your .ini file and define views +that should have API access enabled. + +For example, to enable API access to patch/diff, raw file and archive:: + + api_access_controllers_whitelist = + ChangesetController:changeset_patch, + ChangesetController:changeset_raw, + FilesController:raw, + FilesController:archivefile + +After this change, a Kallithea view can be accessed without login by adding a +GET parameter ``?api_key=`` to the URL. + +Exposing raw diffs is a good way to integrate with +third-party services like code review, or build farms that can download archives. diff -r b4dd4c16c12d -r d89d586b26ae docs/changelog.rst --- a/docs/changelog.rst Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -.. _changelog: - -========= -Changelog -========= - -Kallithea project doesn't keep its changelog here. We refer you to our `Mercurial logs`__. - - -.. __: https://kallithea-scm.org/repos/kallithea/changelog diff -r b4dd4c16c12d -r d89d586b26ae docs/contributing.rst --- a/docs/contributing.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/contributing.rst Sat Dec 24 00:34:38 2016 +0100 @@ -48,9 +48,17 @@ Running tests ------------- -After finishing your changes make sure all tests pass cleanly. You can run -the testsuite running ``nosetests`` from the project root, or if you use tox -run ``tox`` for Python 2.6--2.7 with multiple database test. +After finishing your changes make sure all tests pass cleanly. Install the test +dependencies, then run the testsuite by invoking ``py.test`` from the +project root:: + + pip install -r dev_requirements.txt + py.test + +Note that testing on Python 2.6 also requires ``unittest2``. + +You can also use ``tox`` to run the tests with all supported Python versions +(currently Python 2.6--2.7). When running tests, Kallithea uses `kallithea/tests/test.ini` and populates the SQLite database specified there. @@ -59,31 +67,79 @@ the tests, thus eliminating the initial delay. To achieve this, run the tests as:: paster serve kallithea/tests/test.ini --pid-file=test.pid --daemon - KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 nosetests + KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test kill -9 $(cat test.pid) -You can run individual tests by specifying their path as argument to nosetests. -nosetests also has many more options, see `nosetests -h`. Some useful options +In these commands, the following variables are used:: + + KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests + KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations + +You can run individual tests by specifying their path as argument to py.test. +py.test also has many more options, see `py.test -h`. Some useful options are:: - -x, --stop Stop running tests after the first error or failure - -s, --nocapture Don't capture stdout (any stdout output will be - printed immediately) [NOSE_NOCAPTURE] - --failed Run the tests that failed in the last test run. + -k EXPRESSION only run tests which match the given substring + expression. An expression is a python evaluable + expression where all names are substring-matched + against test names and their parent classes. Example: + -x, --exitfirst exit instantly on first error or failed test. + --lf rerun only the tests that failed at the last run (or + all if none failed) + --ff run all tests but run the last failures first. This + may re-order tests and thus lead to repeated fixture + setup/teardown + --pdb start the interactive Python debugger on errors. + -s, --capture=no don't capture stdout (any stdout output will be + printed immediately) -Coding/contribution guidelines ------------------------------- +Contribution guidelines +----------------------- Kallithea is GPLv3 and we assume all contributions are made by the committer/contributor and under GPLv3 unless explicitly stated. We do care a lot about preservation of copyright and license information for existing code that is brought into the project. +Contributions will be accepted in most formats -- such as pull requests on +Bitbucket, something hosted on your own Kallithea instance, or patches sent by +email to the `kallithea-general`_ mailing list. + +When contributing via Bitbucket, please make your fork of +https://bitbucket.org/conservancy/kallithea/ `non-publishing`_ -- it is one of +the settings on "Repository details" page. This ensures your commits are in +"draft" phase and makes it easier for you to address feedback and for project +maintainers to integrate your changes. + +.. _non-publishing: https://www.mercurial-scm.org/wiki/Phases#Publishing_Repository + +Make sure to test your changes both manually and with the automatic tests +before posting. + +We care about quality and review and keeping a clean repository history. We +might give feedback that requests polishing contributions until they are +"perfect". We might also rebase and collapse and make minor adjustments to your +changes when we apply them. + +We try to make sure we have consensus on the direction the project is taking. +Everything non-sensitive should be discussed in public -- preferably on the +mailing list. We aim at having all non-trivial changes reviewed by at least +one other core developer before pushing. Obvious non-controversial changes will +be handled more casually. + +For now we just have one official branch ("default") and will keep it so stable +that it can be (and is) used in production. Experimental changes should live +elsewhere (for example in a pull request) until they are ready. + + +Coding guidelines +----------------- + We don't have a formal coding/formatting standard. We are currently using a mix of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and -consistency with existing code. Run whitespacecleanup.sh to avoid stupid -whitespace noise in your patches. +consistency with existing code. Run ``scripts/run-all-cleanup`` before +committing to ensure some basic code formatting consistency. We support both Python 2.6.x and 2.7.x and nothing else. For now we don't care about Python 3 compatibility. @@ -112,30 +168,41 @@ .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case -Contributions will be accepted in most formats -- such as pull requests on -bitbucket, something hosted on your own Kallithea instance, or patches sent by -email to the `kallithea-general`_ mailing list. +Template helpers (that is, everything in ``kallithea.lib.helpers``) +should only be referenced from templates. If you need to call a +helper from the Python code, consider moving the function somewhere +else (e.g. to the model). + +Notes on the SQLAlchemy session +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Make sure to test your changes both manually and with the automatic tests -before posting. +Each HTTP request runs inside an independent SQLAlchemy session (as well +as in an independent database transaction). Database model objects +(almost) always belong to a particular SQLAlchemy session, which means +that SQLAlchemy will ensure that they're kept in sync with the database +(but also means that they cannot be shared across requests). -We care about quality and review and keeping a clean repository history. We -might give feedback that requests polishing contributions until they are -"perfect". We might also rebase and collapse and make minor adjustments to your -changes when we apply them. +Objects can be added to the session using ``Session().add``, but this is +rarely needed: -We try to make sure we have consensus on the direction the project is taking. -Everything non-sensitive should be discussed in public -- preferably on the -mailing list. We aim at having all non-trivial changes reviewed by at least -one other core developer before pushing. Obvious non-controversial changes will -be handled more casually. +* When creating a database object by calling the constructor directly, + it must explicitly be added to the session. + +* When creating an object using a factory function (like + ``create_repo``), the returned object has already (by convention) + been added to the session, and should not be added again. -For now we just have one official branch ("default") and will keep it so stable -that it can be (and is) used in production. Experimental changes should live -elsewhere (for example in a pull request) until they are ready. +* When getting an object from the session (via ``Session().query`` or + any of the utility functions that look up objects in the database), + it's already part of the session, and should not be added again. + SQLAlchemy monitors attribute modifications automatically for all + objects it knows about and syncs them to the database. -.. _translations: -.. include:: ./../kallithea/i18n/how_to +SQLAlchemy also flushes changes to the database automatically; manually +calling ``Session().flush`` is usually only necessary when the Python +code needs the database to assign an "auto-increment" primary key ID to +a freshly created model object (before flushing, the ID attribute will +be ``None``). "Roadmap" diff -r b4dd4c16c12d -r d89d586b26ae docs/dev/dbmigrations.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/dev/dbmigrations.rst Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,74 @@ +======================= +Database schema changes +======================= + +Kallithea uses Alembic for :ref:`database migrations ` +(upgrades and downgrades). + +If you are developing a Kallithea feature that requires database schema +changes, you should make a matching Alembic database migration script: + +1. :ref:`Create a Kallithea configuration and database ` for testing + the migration script, or use existing ``development.ini`` setup. + + Ensure that this database is up to date with the latest database + schema *before* the changes you're currently developing. (Do not + create the database while your new schema changes are applied.) + +2. Create a separate throwaway configuration for iterating on the actual + database changes:: + + paster make-config Kallithea temp.ini + + Edit the file to change database settings. SQLite is typically fine, + but make sure to change the path to e.g. ``temp.db``, to avoid + clobbering any existing database file. + +3. Make your code changes (including database schema changes in ``db.py``). + +4. After every database schema change, recreate the throwaway database + to test the changes:: + + rm temp.db + paster setup-db temp.ini --repos=/var/repos --user=doe --email doe@example.com --password=123456 --no-public-access --force-yes + paster repo-scan temp.ini + +5. Once satisfied with the schema changes, auto-generate a draft Alembic + script using the development database that has *not* been upgraded. + (The generated script will upgrade the database to match the code.) + + :: + + alembic -c development.ini revision -m "area: add cool feature" --autogenerate + +6. Edit the script to clean it up and fix any problems. + + Note that for changes that simply add columns, it may be appropriate + to not remove them in the downgrade script (and instead do nothing), + to avoid the loss of data. Unknown columns will simply be ignored by + Kallithea versions predating your changes. + +7. Run ``alembic -c development.ini upgrade head`` to apply changes to + the (non-throwaway) database, and test the upgrade script. Also test + downgrades. + + The included ``development.ini`` has full SQL logging enabled. If + you're using another configuration file, you may want to enable it + by setting ``level = DEBUG`` in section ``[handler_console_sql]``. + +The Alembic migration script should be committed in the same revision as +the database schema (``db.py``) changes. + +See the `Alembic documentation`__ for more information, in particular +the tutorial and the section about auto-generating migration scripts. + +.. __: http://alembic.zzzcomputing.com/en/latest/ + + +Troubleshooting +--------------- + +* If ``alembic --autogenerate`` responds "Target database is not up to + date", you need to either first use Alembic to upgrade the database + to the most recent version (before your changes), or recreate the + database from scratch (without your schema changes applied). diff -r b4dd4c16c12d -r d89d586b26ae docs/dev/translation.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/dev/translation.rst Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,2 @@ +.. _translations: +.. include:: ./../../kallithea/i18n/how_to diff -r b4dd4c16c12d -r d89d586b26ae docs/images/.img diff -r b4dd4c16c12d -r d89d586b26ae docs/index.rst --- a/docs/index.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/index.rst Sat Dec 24 00:34:38 2016 +0100 @@ -23,6 +23,7 @@ installation_iis setup installation_puppet + upgrade **Usage** @@ -44,6 +45,7 @@ usage/backup usage/debugging usage/troubleshooting + usage/customization **Development** @@ -51,7 +53,8 @@ :maxdepth: 1 contributing - changelog + dev/translation + dev/dbmigrations **API** @@ -59,11 +62,10 @@ :maxdepth: 1 api/api - api/models Other topics ------------- +************ * :ref:`genindex` * :ref:`search` diff -r b4dd4c16c12d -r d89d586b26ae docs/installation.rst --- a/docs/installation.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/installation.rst Sat Dec 24 00:34:38 2016 +0100 @@ -26,6 +26,22 @@ have to remove its dependencies manually and make sure that they are not needed by other packages. +Regardless of the installation method you may need to make sure you have +appropriate development packages installed, as installation of some of the +Kallithea dependencies requires a working C compiler and libffi library +headers. Depending on your configuration, you may also need to install +Git and development packages for the database of your choice. + +For Debian and Ubuntu, the following command will ensure that a reasonable +set of dependencies is installed:: + + sudo apt-get install build-essential git python-pip python-virtualenv libffi-dev python-dev + +For Fedora and RHEL-derivatives, the following command will ensure that a +reasonable set of dependencies is installed:: + + sudo yum install gcc git python-pip python-virtualenv libffi-devel python-devel + .. _installation-source: @@ -38,16 +54,13 @@ hg clone https://kallithea-scm.org/repos/kallithea -u stable cd kallithea virtualenv ../kallithea-venv - source ../kallithea-venv/bin/activate + . ../kallithea-venv/bin/activate pip install --upgrade pip setuptools pip install -e . python2 setup.py compile_catalog # for translation of the UI You can now proceed to :ref:`setup`. -To upgrade, simply update the repository with ``hg pull -u`` and restart the -server. - .. _installation-virtualenv: @@ -68,7 +81,7 @@ - Activate the virtualenv_ in your current shell session and make sure the basic requirements are up-to-date by running:: - source /srv/kallithea/venv/bin/activate + . /srv/kallithea/venv/bin/activate pip install --upgrade pip setuptools .. note:: You can't use UNIX ``sudo`` to source the ``virtualenv`` script; it @@ -98,8 +111,8 @@ pip install . -- This will install Kallithea together with pylons_ and all other required - python libraries into the activated virtualenv. +- This will install Kallithea together with all other required + Python libraries into the activated virtualenv. You can now proceed to :ref:`setup`. @@ -123,90 +136,4 @@ You can now proceed to :ref:`setup`. -Upgrading Kallithea from Python Package Index (PyPI) ----------------------------------------------------- - -.. note:: - It is strongly recommended that you **always** perform a database and - configuration backup before doing an upgrade. - - These directions will use '{version}' to note that this is the version of - Kallithea that these files were used with. If backing up your Kallithea - instance from version 0.1 to 0.2, the ``my.ini`` file could be - backed up to ``my.ini.0-1``. - -If using a SQLite database, stop the Kallithea process/daemon/service, and -then make a copy of the database file:: - - service kallithea stop - cp kallithea.db kallithea.db.{version} - -Back up your configuration file:: - - cp my.ini my.ini.{version} - -Ensure that you are using the Python virtual environment that you originally -installed Kallithea in by running:: - - pip freeze - -This will list all packages installed in the current environment. If -Kallithea isn't listed, activate the correct virtual environment:: - - source /srv/kallithea/venv/bin/activate - -Once you have verified the environment you can upgrade Kallithea with:: - - pip install --upgrade kallithea - -Then run the following command from the installation directory:: - - paster make-config Kallithea my.ini - -This will display any changes made by the new version of Kallithea to your -current configuration. It will try to perform an automerge. It is recommended -that you recheck the content after the automerge. - -.. note:: - Please always make sure your .ini files are up to date. Errors can - often be caused by missing parameters added in new versions. - -It is also recommended that you rebuild the whoosh index after upgrading since -the new whoosh version could introduce some incompatible index changes. Please -read the changelog to see if there were any changes to whoosh. - -The final step is to upgrade the database. To do this simply run:: - - paster upgrade-db my.ini - -This will upgrade the schema and update some of the defaults in the database, -and will always recheck the settings of the application, if there are no new -options that need to be set. - -.. note:: - The DB schema upgrade library has some limitations and can sometimes fail if you try to - upgrade from older major releases. In such a case simply run upgrades sequentially, e.g., - upgrading from 0.1.X to 0.3.X should be done like this: 0.1.X. > 0.2.X > 0.3.X - You can always specify what version of Kallithea you want to install for example in pip - `pip install Kallithea==0.2` - -You may find it helpful to clear out your log file so that new errors are -readily apparent:: - - echo > kallithea.log - -Once that is complete, you may now start your upgraded Kallithea Instance:: - - service kallithea start - -Or:: - - paster serve /srv/kallithea/my.ini - -.. note:: - If you're using Celery, make sure you restart all instances of it after - upgrade. - - .. _virtualenv: http://pypi.python.org/pypi/virtualenv -.. _pylons: http://www.pylonsproject.org/ diff -r b4dd4c16c12d -r d89d586b26ae docs/installation_iis.rst --- a/docs/installation_iis.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/installation_iis.rst Sat Dec 24 00:34:38 2016 +0100 @@ -39,7 +39,7 @@ own virtual folder will be noted where appropriate. Application pool -................ +^^^^^^^^^^^^^^^^ Make sure that there is a unique application pool for the Kallithea application with an identity that has read access to the Kallithea distribution. @@ -55,7 +55,7 @@ as long as the Kallithea requirements are met by the existing pool. ISAPI handler -............. +^^^^^^^^^^^^^ The ISAPI handler can be generated using:: @@ -79,7 +79,7 @@ site will be processed through this logic henceforth. Authentication with Kallithea using IIS authentication modules -.............................................................. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The recommended way to handle authentication with Kallithea using IIS is to let IIS handle all the authentication and just pass it to Kallithea. @@ -126,4 +126,4 @@ traces, making it a lot easier to identify issues. -.. _hgssoauthenticatio: https://bitbucket.org/domruf/hgssoauthentication +.. _hgssoauthentication: https://bitbucket.org/domruf/hgssoauthentication diff -r b4dd4c16c12d -r d89d586b26ae docs/installation_puppet.rst --- a/docs/installation_puppet.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/installation_puppet.rst Sat Dec 24 00:34:38 2016 +0100 @@ -90,7 +90,7 @@ parameter in the :ref:`example above `, but there are more. For example, you can specify the installation directory, the name of the user under which Kallithea gets installed, the initial admin password, etc. -Notably, you can provide arbitrary modifications to Kallitheas configuration +Notably, you can provide arbitrary modifications to Kallithea's configuration file by means of the ``config_hash`` parameter. Parameters, which have not been set explicitly, will be set to default values, diff -r b4dd4c16c12d -r d89d586b26ae docs/installation_win.rst --- a/docs/installation_win.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/installation_win.rst Sat Dec 24 00:34:38 2016 +0100 @@ -1,12 +1,12 @@ .. _installation_win: -================================================================ -Installation and upgrade on Windows (7/Server 2008 R2 and newer) -================================================================ +==================================================== +Installation on Windows (7/Server 2008 R2 and newer) +==================================================== First time install -:::::::::::::::::: +------------------ Target OS: Windows 7 and newer or Windows Server 2008 R2 and newer @@ -15,7 +15,7 @@ To install on an older version of Windows, see ``_ Step 1 -- Install Python ------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^ Install Python 2.x.y (x = 6 or 7). Latest version is recommended. If you need another version, they can run side by side. @@ -31,7 +31,7 @@ be needed in the next step. In this case, it is "2.7". Step 2 -- Python BIN --------------------- +^^^^^^^^^^^^^^^^^^^^ Add Python BIN folder to the path. This can be done manually (editing "PATH" environment variable) or by using Windows Support Tools that @@ -45,7 +45,7 @@ path. Typically this is ``C:\\Python27``. Step 3 -- Install pywin32 extensions ------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Download pywin32 from: http://sourceforge.net/projects/pywin32/files/ @@ -61,7 +61,7 @@ (Win32) Step 4 -- Install pip ---------------------- +^^^^^^^^^^^^^^^^^^^^^ pip is a package management system for Python. You will need it to install Kallithea and its dependencies. @@ -85,7 +85,7 @@ SETX PATH "%PATH%;[your-python-path]\Scripts" /M Step 5 -- Kallithea folder structure ------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Create a Kallithea folder structure. @@ -102,7 +102,7 @@ C:\Kallithea\Repos Step 6 -- Install virtualenv ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. note:: A python virtual environment will allow for isolation between the Python packages of your system and those used for Kallithea. @@ -119,7 +119,7 @@ virtualenv C:\Kallithea\Env Step 7 -- Install Kallithea ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ In order to install Kallithea, you need to be able to run "pip install kallithea". It will use pip to install the Kallithea Python package and its dependencies. Some Python packages use managed code and need to be compiled. @@ -145,17 +145,19 @@ complete. Some warnings will appear. Don't worry, they are normal. -Step 8 -- Install git (optional) --------------------------------- +Step 8 -- Install Git (optional) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Mercurial being a python package, was installed automatically when doing ``pip install kallithea``. -Mercurial being a python package, it was installed automatically when doing "pip install kallithea". - -You need to install git manually if you want Kallithea to be able to host git repositories. - +You need to install Git manually if you want Kallithea to be able to host Git repositories. See http://git-scm.com/book/en/v2/Getting-Started-Installing-Git#Installing-on-Windows for instructions. +The location of the Git binaries (like ``c:\path\to\git\bin``) must be +added to the ``PATH`` environment variable so ``git.exe`` and other tools like +``gzip.exe`` are available. Step 9 -- Configuring Kallithea -------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Steps taken from ``_ @@ -178,8 +180,8 @@ paster setup-db production.ini .. warning:: This time a *new* database will be installed. You must - follow a different step to later *upgrade* to a newer - Kallithea version) + follow a different process to later :ref:`upgrade ` + to a newer Kallithea version. The script will ask you for confirmation about creating a new database, answer yes (y) @@ -191,10 +193,10 @@ If you make a mistake and the script doesn't end, don't worry: start it again. -If you decided not to install git, you will get errors about it that you can ignore. +If you decided not to install Git, you will get errors about it that you can ignore. Step 10 -- Running Kallithea ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the previous command prompt, being in the C:\\Kallithea\\Bin folder, type:: @@ -219,27 +221,3 @@ - Using Apache. You can investigate here: - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4 - - -Upgrading -::::::::: - -Stop running Kallithea -Open a CommandPrompt like in Step 7 (cd to C:\Kallithea\Env\Scripts and activate) and type:: - - pip install kallithea --upgrade - cd \Kallithea\Bin - -Backup your production.ini file now. - -Then run:: - - paster make-config Kallithea production.ini - -Look for changes and update your production.ini accordingly. - -Next, update the database:: - - paster upgrade-db production.ini - -More details can be found in ``_. diff -r b4dd4c16c12d -r d89d586b26ae docs/installation_win_old.rst --- a/docs/installation_win_old.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/installation_win_old.rst Sat Dec 24 00:34:38 2016 +0100 @@ -1,12 +1,12 @@ .. _installation_win_old: -====================================================================== -Installation and upgrade on Windows (XP/Vista/Server 2003/Server 2008) -====================================================================== +========================================================== +Installation on Windows (XP/Vista/Server 2003/Server 2008) +========================================================== First-time install -:::::::::::::::::: +------------------ Target OS: Windows XP SP3 32-bit English (Clean installation) + All Windows Updates until 24-may-2012 @@ -24,7 +24,7 @@ - http://bugs.python.org/issue7511 Step 1 -- Install Visual Studio 2008 Express --------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Optional: You can also install MinGW, but VS2008 installation is easier. @@ -58,7 +58,7 @@ Copy C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat to C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64\vcvarsamd64.bat Step 2 -- Install Python ------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^ Install Python 2.x.y (x = 6 or 7) x86 version (32-bit). DO NOT USE A 3.x version. Download Python 2.x.y from: @@ -74,7 +74,7 @@ 64-bit: Just download and install the 64-bit version of python. Step 3 -- Install Win32py extensions ------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Download pywin32 from: http://sourceforge.net/projects/pywin32/files/ @@ -93,7 +93,7 @@ http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download Step 4 -- Python BIN --------------------- +^^^^^^^^^^^^^^^^^^^^ Add Python BIN folder to the path @@ -120,7 +120,7 @@ Typically: C:\\Python27 Step 5 -- Kallithea folder structure ------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Create a Kallithea folder structure @@ -137,7 +137,7 @@ C:\Kallithea\Repos Step 6 -- Install virtualenv ----------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Install Virtual Env for Python @@ -157,7 +157,7 @@ to include it) Step 7 -- Install Kallithea ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ Finally, install Kallithea @@ -195,7 +195,7 @@ Some warnings will appear, don't worry as they are normal. Step 8 -- Configuring Kallithea -------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ steps taken from http://packages.python.org/Kallithea/setup.html @@ -217,8 +217,9 @@ paster setup-db production.ini -(this time a NEW database will be installed, you must follow a different -step to later UPGRADE to a newer Kallithea version) +.. warning:: This time a *new* database will be installed. You must + follow a different process to later :ref:`upgrade ` + to a newer Kallithea version. The script will ask you for confirmation about creating a NEW database, answer yes (y) @@ -233,7 +234,7 @@ it again. Step 9 -- Running Kallithea ---------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the previous command prompt, being in the C:\\Kallithea\\Bin folder, just type:: @@ -260,23 +261,3 @@ - Using Apache. You can investigate here: - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4 - - -Upgrading -::::::::: - -Stop running Kallithea -Open a CommandPrompt like in Step7 (VS2008 path + activate) and type:: - - easy_install -U kallithea - cd \Kallithea\Bin - -{ backup your production.ini file now} :: - - paster make-config Kallithea production.ini - -(check changes and update your production.ini accordingly) :: - - paster upgrade-db production.ini (update database) - -Full steps in http://packages.python.org/Kallithea/upgrade.html diff -r b4dd4c16c12d -r d89d586b26ae docs/make.bat --- a/docs/make.bat Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/make.bat Sat Dec 24 00:34:38 2016 +0100 @@ -3,153 +3,153 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build + set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end ) if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end ) if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end ) if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end ) if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end ) if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end ) if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - echo. - echo.Build finished; now you can process the JSON files. - goto end + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end ) if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. - goto end + goto end ) if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Kallithea.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Kallithea.ghc - goto end + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Kallithea.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Kallithea.ghc + goto end ) if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - echo. - echo.Build finished. - goto end + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + echo. + echo.Build finished. + goto end ) if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end ) if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end ) if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end ) if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end ) if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end ) if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. - goto end + goto end ) if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. - goto end + goto end ) :end diff -r b4dd4c16c12d -r d89d586b26ae docs/overview.rst --- a/docs/overview.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/overview.rst Sat Dec 24 00:34:38 2016 +0100 @@ -136,6 +136,5 @@ .. _iis: http://en.wikipedia.org/wiki/Internet_Information_Services .. _pip: http://en.wikipedia.org/wiki/Pip_%28package_manager%29 .. _WSGI: http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface -.. _pylons: http://www.pylonsproject.org/ .. _HAProxy: http://www.haproxy.org/ .. _Varnish: https://www.varnish-cache.org/ diff -r b4dd4c16c12d -r d89d586b26ae docs/setup.rst --- a/docs/setup.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/setup.rst Sat Dec 24 00:34:38 2016 +0100 @@ -67,24 +67,6 @@ repositories. -Extensions ----------- - -Optionally one can create an ``rcextensions`` package that extends Kallithea -functionality. -To generate a skeleton extensions package, run:: - - paster make-rcext my.ini - -This will create an ``rcextensions`` package next to the specified ``ini`` file. -With ``rcextensions`` it's possible to add additional mapping for whoosh, -stats and add additional code into the push/pull/create/delete repo hooks, -for example for sending signals to build-bots such as Jenkins. - -See the ``__init__.py`` file inside the generated ``rcextensions`` package -for more details. - - Using Kallithea with SSH ------------------------ @@ -135,7 +117,7 @@ paster make-index my.ini -f -The ``--repo-location`` option allows the location of the repositories to be overriden; +The ``--repo-location`` option allows the location of the repositories to be overridden; usually, the location is retrieved from the Kallithea database. The ``--index-only`` option can be used to limit the indexed repositories to a comma-separated list:: @@ -157,6 +139,7 @@ .. _ldap-setup: + Setting up LDAP support ----------------------- @@ -178,7 +161,6 @@ Connection settings Enable LDAP = checked Host = host.example.com - Port = 389 Account = Password = Connection Security = LDAPS connection @@ -215,8 +197,9 @@ .. _Port: -Port : required - 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP. +Port : optional + Defaults to 389 for PLAIN un-encrypted LDAP and START_TLS. + Defaults to 636 for LDAPS. .. _ldap_account: @@ -236,26 +219,27 @@ Connection Security : required Defines the connection to LDAP server - No encryption - Plain non encrypted connection + PLAIN + Plain unencrypted LDAP connection. + This will by default use `Port`_ 389. - LDAPS connection - Enable LDAPS connections. It will likely require `Port`_ to be set to - a different value (standard LDAPS port is 636). When LDAPS is enabled - then `Certificate Checks`_ is required. + LDAPS + Use secure LDAPS connections according to `Certificate + Checks`_ configuration. + This will by default use `Port`_ 636. - START_TLS on LDAP connection - START TLS connection + START_TLS + Use START TLS according to `Certificate Checks`_ configuration on an + apparently "plain" LDAP connection. + This will by default use `Port`_ 389. .. _Certificate Checks: Certificate Checks : optional How SSL certificates verification is handled -- this is only useful when `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security - while the other options are susceptible to man-in-the-middle attacks. SSL - certificates can be installed to /etc/openldap/cacerts so that the - DEMAND or HARD options can be used with self-signed certificates or - certificates that do not have traceable certificates of authority. + with mandatory certificate validation, while the other options are + susceptible to man-in-the-middle attacks. NEVER A serve certificate will never be requested or checked. @@ -277,6 +261,16 @@ HARD The same as DEMAND. +.. _Custom CA Certificates: + +Custom CA Certificates : optional + Directory used by OpenSSL to find CAs for validating the LDAP server certificate. + Python 2.7.10 and later default to using the system certificate store, and + this should thus not be necessary when using certificates signed by a CA + trusted by the system. + It can be set to something like `/etc/openldap/cacerts` on older systems or + if using self-signed certificates. + .. _Base DN: Base DN : required @@ -347,7 +341,7 @@ will be saved there. Active Directory -'''''''''''''''' +^^^^^^^^^^^^^^^^ Kallithea can use Microsoft Active Directory for user authentication. This is done through an LDAP or LDAPS connection to Active Directory. The @@ -384,7 +378,7 @@ permissions before the user logs in for the first time, using the :ref:`create-user` API. Container-based authentication -'''''''''''''''''''''''''''''' +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In a container-based authentication setup, Kallithea reads the user name from the ``REMOTE_USER`` server variable provided by the WSGI container. @@ -394,7 +388,7 @@ Kallithea. Proxy pass-through authentication -''''''''''''''''''''''''''''''''' +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In a proxy pass-through authentication setup, Kallithea reads the user name from the ``X-Forwarded-User`` request header, which should be configured to be @@ -428,6 +422,73 @@ RequestHeader set X-Forwarded-User %{RU}e +Setting metadata in container/reverse-proxy +""""""""""""""""""""""""""""""""""""""""""" +When a new user account is created on the first login, Kallithea has no information about +the user's email and full name. So you can set some additional request headers like in the +example below. In this example the user is authenticated via Kerberos and an Apache +mod_python fixup handler is used to get the user information from a LDAP server. But you +could set the request headers however you want. + +.. code-block:: apache + + + ProxyPass http://127.0.0.1:5000/someprefix + ProxyPassReverse http://127.0.0.1:5000/someprefix + SetEnvIf X-Url-Scheme https HTTPS=1 + + AuthName "Kerberos Login" + AuthType Kerberos + Krb5Keytab /etc/apache2/http.keytab + KrbMethodK5Passwd off + KrbVerifyKDC on + Require valid-user + + PythonFixupHandler ldapmetadata + + RequestHeader set X_REMOTE_USER %{X_REMOTE_USER}e + RequestHeader set X_REMOTE_EMAIL %{X_REMOTE_EMAIL}e + RequestHeader set X_REMOTE_FIRSTNAME %{X_REMOTE_FIRSTNAME}e + RequestHeader set X_REMOTE_LASTNAME %{X_REMOTE_LASTNAME}e + + +.. code-block:: python + + from mod_python import apache + import ldap + + LDAP_SERVER = "ldap://server.mydomain.com:389" + LDAP_USER = "" + LDAP_PASS = "" + LDAP_ROOT = "dc=mydomain,dc=com" + LDAP_FILTER = "sAMAccountName=%s" + LDAP_ATTR_LIST = ['sAMAccountName','givenname','sn','mail'] + + def fixuphandler(req): + if req.user is None: + # no user to search for + return apache.OK + else: + try: + if('\\' in req.user): + username = req.user.split('\\')[1] + elif('@' in req.user): + username = req.user.split('@')[0] + else: + username = req.user + l = ldap.initialize(LDAP_SERVER) + l.simple_bind_s(LDAP_USER, LDAP_PASS) + r = l.search_s(LDAP_ROOT, ldap.SCOPE_SUBTREE, LDAP_FILTER % username, attrlist=LDAP_ATTR_LIST) + + req.subprocess_env['X_REMOTE_USER'] = username + req.subprocess_env['X_REMOTE_EMAIL'] = r[0][1]['mail'][0].lower() + req.subprocess_env['X_REMOTE_FIRSTNAME'] = "%s" % r[0][1]['givenname'][0] + req.subprocess_env['X_REMOTE_LASTNAME'] = "%s" % r[0][1]['sn'][0] + except Exception, e: + apache.log_error("error getting data from ldap %s" % str(e), apache.APLOG_ERR) + + return apache.OK + .. note:: If you enable proxy pass-through authentication, make sure your server is only accessible through the proxy. Otherwise, any client would be able to @@ -606,7 +667,7 @@ ## uncomment root directive if you want to serve static files by nginx ## requires static_files = false in .ini file - #root /path/to/installation/kallithea/public; + #root /srv/kallithea/kallithea/kallithea/public; include /etc/nginx/proxy.conf; location / { try_files $uri @kallithea; @@ -661,7 +722,7 @@ #important ! - #Directive to properly generate url (clone url) for pylons + #Directive to properly generate url (clone url) for Kallithea ProxyPreserveHost On #kallithea instance @@ -735,8 +796,7 @@ .. code-block:: apache - WSGIDaemonProcess kallithea \ - threads=4 \ + WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100 \ python-home=/srv/kallithea/venv WSGIProcessGroup kallithea WSGIScriptAlias / /srv/kallithea/dispatch.wsgi @@ -746,7 +806,7 @@ .. code-block:: apache - WSGIDaemonProcess kallithea threads=4 + WSGIDaemonProcess kallithea processes=5 threads=1 maximum-requests=100 WSGIProcessGroup kallithea WSGIScriptAlias / /srv/kallithea/dispatch.wsgi WSGIPassAuthorization On @@ -756,11 +816,6 @@ directory owned by a different user, use the user and group options to WSGIDaemonProcess to set the name of the user and group. -.. note:: - If running Kallithea in multiprocess mode, - make sure you set ``instance_id = *`` in the configuration so each process - gets it's own cache invalidation key. - Example WSGI dispatch script: .. code-block:: python @@ -769,7 +824,7 @@ os.environ["HGENCODING"] = "UTF-8" os.environ['PYTHON_EGG_CACHE'] = '/srv/kallithea/.egg-cache' - # sometimes it's needed to set the curent dir + # sometimes it's needed to set the current dir os.chdir('/srv/kallithea/') import site diff -r b4dd4c16c12d -r d89d586b26ae docs/theme/nature/static/nature.css_t --- a/docs/theme/nature/static/nature.css_t Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/theme/nature/static/nature.css_t Sat Dec 24 00:34:38 2016 +0100 @@ -2,11 +2,11 @@ * Sphinx stylesheet -- default theme * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - + @import url("basic.css"); - + /* -- page layout ----------------------------------------------------------- */ - + body { font-family: Arial, sans-serif; font-size: 100%; @@ -28,18 +28,18 @@ hr{ border: 1px solid #B1B4B6; } - + div.document { background-color: #eee; } - + div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; font-size: 0.8em; } - + div.footer { color: #555; width: 100%; @@ -47,12 +47,12 @@ text-align: center; font-size: 75%; } - + div.footer a { color: #444; text-decoration: underline; } - + div.related { background-color: #577632; line-height: 32px; @@ -60,11 +60,11 @@ text-shadow: 0px 1px 0 #444; font-size: 0.80em; } - + div.related a { color: #E2F3CC; } - + div.sphinxsidebar { font-size: 0.75em; line-height: 1.5em; @@ -73,7 +73,7 @@ div.sphinxsidebarwrapper{ padding: 20px 0; } - + div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: Arial, sans-serif; @@ -89,30 +89,29 @@ div.sphinxsidebar h4{ font-size: 1.1em; } - + div.sphinxsidebar h3 a { color: #444; } - - + div.sphinxsidebar p { color: #888; padding: 5px 20px; } - + div.sphinxsidebar p.topless { } - + div.sphinxsidebar ul { margin: 10px 20px; padding: 0; color: #000; } - + div.sphinxsidebar a { color: #444; } - + div.sphinxsidebar input { border: 1px solid #ccc; font-family: sans-serif; @@ -126,19 +125,19 @@ div.sphinxsidebar input[type=image] { border: 0; } - + /* -- body styles ----------------------------------------------------------- */ - + a { color: #005B81; text-decoration: none; } - + a:hover { color: #E32E00; text-decoration: underline; } - + div.body h1, div.body h2, div.body h3, @@ -153,30 +152,30 @@ padding: 5px 0 5px 10px; text-shadow: 0px 1px 0 white } - + div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } div.body h2 { font-size: 150%; background-color: #C8D5E3; } div.body h3 { font-size: 120%; background-color: #D8DEE3; } div.body h4 { font-size: 110%; background-color: #D8DEE3; } div.body h5 { font-size: 100%; background-color: #D8DEE3; } div.body h6 { font-size: 100%; background-color: #D8DEE3; } - + a.headerlink { color: #c60f0f; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; } - + a.headerlink:hover { background-color: #c60f0f; color: white; } - + div.body p, div.body dd, div.body li { line-height: 1.5em; } - + div.admonition p.admonition-title + p { display: inline; } @@ -189,29 +188,29 @@ background-color: #eee; border: 1px solid #ccc; } - + div.seealso { background-color: #ffc; border: 1px solid #ff6; } - + div.topic { background-color: #eee; } - + div.warning { background-color: #ffe4e4; border: 1px solid #f66; } - + p.admonition-title { display: inline; } - + p.admonition-title:after { content: ":"; } - + pre { padding: 10px; background-color: White; @@ -222,7 +221,7 @@ margin: 1.5em 0 1.5em 0; box-shadow: 1px 1px 1px #d8d8d8; } - + tt { background-color: #ecf0f3; color: #222; diff -r b4dd4c16c12d -r d89d586b26ae docs/upgrade.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/upgrade.rst Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,187 @@ +.. _upgrade: + +=================== +Upgrading Kallithea +=================== + +This describes the process for upgrading Kallithea, independently of the +Kallithea installation method. + +.. note:: + If you are upgrading from a RhodeCode installation, you must first + install Kallithea 0.3.2 and follow the instructions in the 0.3.2 + README to perform a one-time conversion of the database from + RhodeCode to Kallithea, before upgrading to the latest version + of Kallithea. + + +1. Stop the Kallithea web application +------------------------------------- + +This step depends entirely on the web server software used to serve +Kallithea, but in any case, Kallithea should not be running during +the upgrade. + +.. note:: + If you're using Celery, make sure you stop all instances during the + upgrade. + + +2. Create a backup of both database and configuration +----------------------------------------------------- + +You are of course strongly recommended to make backups regularly, but it +is *especially* important to make a full database and configuration +backup before performing a Kallithea upgrade. + +Back up your configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Make a copy of your Kallithea configuration (``.ini``) file. + +If you are using :ref:`rcextensions `, you should also +make a copy of the entire ``rcextensions`` directory. + +Back up your database +^^^^^^^^^^^^^^^^^^^^^ + +If using SQLite, simply make a copy of the Kallithea database (``.db``) +file. + +If using PostgreSQL, please consult the documentation for the ``pg_dump`` +utility. + +If using MySQL, please consult the documentation for the ``mysqldump`` +utility. + +Look for ``sqlalchemy.url`` in your configuration file to determine +database type, settings, location, etc. + + +3. Activate the Kallithea virtual environment (if any) +------------------------------------------------------ + +Verify that you are using the Python environment that you originally +installed Kallithea in by running:: + + pip freeze + +This will list all packages installed in the current environment. If +Kallithea isn't listed, activate the correct virtual environment. +See the appropriate installation page for details. + + +4. Install new version of Kallithea +----------------------------------- + +Please refer to the instructions for the installation method you +originally used to install Kallithea. + +If you originally installed using pip, it is as simple as:: + + pip install --upgrade kallithea + +If you originally installed from version control, it is as simple as:: + + cd my-kallithea-clone + hg pull -u + pip install -e . + + +5. Upgrade your configuration +----------------------------- + +Run the following command to upgrade your configuration (``.ini``) file:: + + paster make-config Kallithea my.ini + +This will display any changes made by the new version of Kallithea to your +current configuration, and attempt an automatic merge. It is recommended +that you check the contents after the merge. + +.. note:: + Please always make sure your ``.ini`` files are up to date. Errors + can often be caused by missing parameters added in new versions. + +.. _upgrade_db: + + +6. Upgrade your database +------------------------ + +.. note:: + If you are *downgrading* Kallithea, you should perform the database + migration step *before* installing the older version. (That is, + always perform migrations using the most recent of the two versions + you're migrating between.) + +First, run the following command to see your current database version:: + + alembic -c my.ini current + +Typical output will be something like "9358dc3d6828 (head)", which is +the current Alembic database "revision ID". Write down the entire output +for troubleshooting purposes. + +The output will be empty if you're upgrading from Kallithea 0.3.x or +older. That's expected. If you get an error that the config file was not +found or has no ``[alembic]`` section, see the next section. + +Next, if you are performing an *upgrade*: Run the following command to +upgrade your database to the current Kallithea version:: + + alembic -c my.ini upgrade head + +If you are performing a *downgrade*: Run the following command to +downgrade your database to the given version:: + + alembic -c my.ini downgrade 0.4 + +Alembic will show the necessary migrations (if any) as it executes them. +If no "ERROR" is displayed, the command was successful. + +Should an error occur, the database may be "stranded" half-way +through the migration, and you should restore it from backup. + +Enabling old Kallithea config files for Alembic use +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Kallithea configuration files created before the introduction of Alembic +(i.e. predating Kallithea 0.4) need to be updated for use with Alembic. +Without this, Alembic will fail with an error like this:: + + FAILED: No config file 'my.ini' found, or file has no '[alembic]' section + +If Alembic complains specifically about a missing ``alembic.ini``, it is +likely because you did not specify a config file using the ``-c`` option. +On the other hand, if the mentioned config file actually exists, you +need to append the following lines to it:: + + [alembic] + script_location = kallithea:alembic + +Your config file should now work with Alembic. + + +7. Rebuild the Whoosh full-text index +------------------------------------- + +It is recommended that you rebuild the Whoosh index after upgrading since +new Whoosh versions can introduce incompatible index changes. + + +8. Start the Kallithea web application +-------------------------------------- + +This step once again depends entirely on the web server software used to +serve Kallithea. + +Before starting the new version of Kallithea, you may find it helpful to +clear out your log file so that new errors are readily apparent. + +.. note:: + If you're using Celery, make sure you restart all instances of it after + upgrade. + + +.. _virtualenv: http://pypi.python.org/pypi/virtualenv diff -r b4dd4c16c12d -r d89d586b26ae docs/usage/customization.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/usage/customization.rst Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,50 @@ +.. _customization: + +============= +Customization +============= + +There are several ways to customize Kallithea to your needs depending on what +you want to achieve. + + +HTML/JavaScript/CSS customization +--------------------------------- + +To customize the look-and-feel of the web interface (for example to add a +company banner or some JavaScript widget or to tweak the CSS style definitions) +you can enter HTML code (possibly with JavaScript and/or CSS) directly via the +*Admin > Settings > Global > HTML/JavaScript customization +block*. + + +Behavioral customization: rcextensions +-------------------------------------- + +Some behavioral customization can be done in Python using ``rcextensions``, a +custom Python package that can extend Kallithea functionality. + +With ``rcextensions`` it's possible to add additional mappings for Whoosh +indexing and statistics, to add additional code into the push/pull/create/delete +repository hooks (for example to send signals to build bots such as Jenkins) and +even to monkey-patch certain parts of the Kallithea source code (for example +overwrite an entire function, change a global variable, ...). + +To generate a skeleton extensions package, run:: + + paster make-rcext my.ini + +This will create an ``rcextensions`` package next to the specified ``ini`` file. +See the ``__init__.py`` file inside the generated ``rcextensions`` package +for more details. + + +Behavioral customization: code changes +-------------------------------------- + +As Kallithea is open-source software, you can make any changes you like directly +in the source code. + +We encourage you to send generic improvements back to the +community so that Kallithea can become better. See :ref:`contributing` for more +details. diff -r b4dd4c16c12d -r d89d586b26ae docs/usage/debugging.rst --- a/docs/usage/debugging.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/usage/debugging.rst Sat Dec 24 00:34:38 2016 +0100 @@ -25,7 +25,7 @@ To enable interactive debug mode simply comment out ``set debug = false`` in the .ini file. This will trigger an interactive debugger each time -there is an error in the browser, or send a http link if an error occured in the backend. This +there is an error in the browser, or send a http link if an error occurred in the backend. This is a great tool for fast debugging as you get a handy Python console right in the web view. diff -r b4dd4c16c12d -r d89d586b26ae docs/usage/email.rst --- a/docs/usage/email.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/usage/email.rst Sat Dec 24 00:34:38 2016 +0100 @@ -12,8 +12,17 @@ Before any email can be sent, an SMTP server has to be configured using the configuration file setting ``smtp_server``. If required for that server, specify a username (``smtp_username``) and password (``smtp_password``), a non-standard -port (``smtp_port``), encryption settings (``smtp_use_tls`` or ``smtp_use_ssl``) -and/or specific authentication parameters (``smtp_auth``). +port (``smtp_port``), whether to use "SSL" when connecting (``smtp_use_ssl``) +or use STARTTLS (``smtp_use_tls``), and/or specify special ESMTP "auth" features +(``smtp_auth``). + +For example, for sending through gmail, use:: + + smtp_server = smtp.gmail.com + smtp_username = username + smtp_password = password + smtp_port = 465 + smtp_use_ssl = true Application emails @@ -48,6 +57,19 @@ The subject of these emails can optionally be prefixed with the value of ``email_prefix`` in the configuration file. +A Kallithea-specific header indicating the email type will be added to each +email. This header can be used for email filtering. The header is of the form: + + X-Kallithea-Notification-Type: + +where ```` is one of: + +- ``pull_request``: you are invited as reviewer in a pull request +- ``pull_request_comment``: a comment was given on a pull request +- ``cs_comment``: a comment was given on a changeset +- ``registration``: a new user was registered +- ``message``: another type of email + Error emails ------------ diff -r b4dd4c16c12d -r d89d586b26ae docs/usage/general.rst --- a/docs/usage/general.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/usage/general.rst Sat Dec 24 00:34:38 2016 +0100 @@ -151,7 +151,7 @@ features that merit further explanation. Repository extra fields -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ In the *Visual* tab, there is an option "Use repository extra fields", which allows to set custom fields for each repository in the system. @@ -165,7 +165,7 @@ Newly created fields are accessible via the API. Meta tagging -~~~~~~~~~~~~ +^^^^^^^^^^^^ In the *Visual* tab, option "Stylify recognised meta tags" will cause Kallithea to turn certain text fragments in repository and repository group diff -r b4dd4c16c12d -r d89d586b26ae docs/usage/performance.rst --- a/docs/usage/performance.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/usage/performance.rst Sat Dec 24 00:34:38 2016 +0100 @@ -4,48 +4,72 @@ Optimizing Kallithea performance ================================ -When serving a large amount of big repositories, Kallithea can start -performing slower than expected. Because of the demanding nature of handling large -amounts of data from version control systems, here are some tips on how to get -the best performance. +When serving a large amount of big repositories, Kallithea can start performing +slower than expected. Because of the demanding nature of handling large amounts +of data from version control systems, here are some tips on how to get the best +performance. + -* Kallithea is often I/O bound, and hence a fast disk (SSD/SAN) is - usually more important than a fast CPU. +Fast storage +------------ + +Kallithea is often I/O bound, and hence a fast disk (SSD/SAN) and plenty of RAM +is usually more important than a fast CPU. + -* Sluggish loading of the front page can easily be fixed by grouping repositories or by - increasing cache size (see below). This includes using the lightweight dashboard - option and ``vcs_full_cache`` setting in .ini file. +Caching +------- + +Tweak beaker cache settings in the ini file. The actual effect of that is +questionable. + -Follow these few steps to improve performance of Kallithea system. +Database +-------- -1. Increase cache +SQLite is a good option when having a small load on the system. But due to +locking issues with SQLite, it is not recommended to use it for larger +deployments. - Tweak beaker cache settings in the ini file. The actual effect of that - is questionable. +Switching to MySQL or PostgreSQL will result in an immediate performance +increase. A tool like SQLAlchemyGrate_ can be used for migrating to another +database platform. + -2. Switch from SQLite to PostgreSQL or MySQL +Horizontal scaling +------------------ + +Scaling horizontally means running several Kallithea instances and let them +share the load. That can give huge performance benefits when dealing with large +amounts of traffic (many users, CI servers, etc.). Kallithea can be scaled +horizontally on one (recommended) or multiple machines. - SQLite is a good option when having a small load on the system. But due to - locking issues with SQLite, it is not recommended to use it for larger - deployments. Switching to MySQL or PostgreSQL will result in an immediate - performance increase. A tool like SQLAlchemyGrate_ can be used for - migrating to another database platform. +It is generally possible to run WSGI applications multithreaded, so that +several HTTP requests are served from the same Python process at once. That can +in principle give better utilization of internal caches and less process +overhead. + +One danger of running multithreaded is that program execution becomes much more +complex; programs must be written to consider all combinations of events and +problems might depend on timing and be impossible to reproduce. -3. Scale Kallithea horizontally +Kallithea can't promise to be thread-safe, just like the embedded Mercurial +backend doesn't make any strong promises when used as Kallithea uses it. +Instead, we recommend scaling by using multiple server processes. - Scaling horizontally can give huge performance benefits when dealing with - large amounts of traffic (many users, CI servers, etc.). Kallithea can be - scaled horizontally on one (recommended) or multiple machines. In order - to scale horizontally you need to do the following: +Web servers with multiple worker processes (such as ``mod_wsgi`` with the +``WSGIDaemonProcess`` ``processes`` parameter) will work out of the box. - - Each instance needs its own .ini file and unique ``instance_id`` set. +In order to scale horizontally on multiple machines, you need to do the +following: + - Each instance's ``data`` storage needs to be configured to be stored on a shared disk storage, preferably together with repositories. This ``data`` dir contains template caches, sessions, whoosh index and is used for task locking (so it is safe across multiple instances). Set the ``cache_dir``, ``index_dir``, ``beaker.cache.data_dir``, ``beaker.cache.lock_dir`` variables in each .ini file to a shared location across Kallithea instances - - If celery is used each instance should run a separate Celery instance, but + - If using several Celery instances, the message broker should be common to all of them (e.g., one shared RabbitMQ server) - Load balance using round robin or IP hash, recommended is writing LB rules @@ -53,4 +77,42 @@ servers or build bots. +Serve static files directly from the web server +----------------------------------------------- + +With the default ``static_files`` ini setting, the Kallithea WSGI application +will take care of serving the static files from ``kallithea/public/`` at the +root of the application URL. + +The actual serving of the static files is very fast and unlikely to be a +problem in a Kallithea setup - the responses generated by Kallithea from +database and repository content will take significantly more time and +resources. + +To serve static files from the web server, use something like this Apache config +snippet:: + + Alias /images/ /srv/kallithea/kallithea/kallithea/public/images/ + Alias /css/ /srv/kallithea/kallithea/kallithea/public/css/ + Alias /js/ /srv/kallithea/kallithea/kallithea/public/js/ + Alias /codemirror/ /srv/kallithea/kallithea/kallithea/public/codemirror/ + Alias /fontello/ /srv/kallithea/kallithea/kallithea/public/fontello/ + +Then disable serving of static files in the ``.ini`` ``app:main`` section:: + + static_files = false + +If using Kallithea installed as a package, you should be able to find the files +under ``site-packages/kallithea``, either in your Python installation or in your +virtualenv. When upgrading, make sure to update the web server configuration +too if necessary. + +It might also be possible to improve performance by configuring the web server +to compress responses (served from static files or generated by Kallithea) when +serving them. That might also imply buffering of responses - that is more +likely to be a problem; large responses (clones or pulls) will have to be fully +processed and spooled to disk or memory before the client will see any +response. See the documentation for your web server. + + .. _SQLAlchemyGrate: https://github.com/shazow/sqlalchemygrate diff -r b4dd4c16c12d -r d89d586b26ae docs/usage/troubleshooting.rst --- a/docs/usage/troubleshooting.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/usage/troubleshooting.rst Sat Dec 24 00:34:38 2016 +0100 @@ -63,7 +63,7 @@ | :Q: **Requests hanging on Windows** -:A: Please try out with disabled Antivirus software, there are some known problems with Eset Anitivirus. Make sure +:A: Please try out with disabled Antivirus software, there are some known problems with Eset Antivirus. Make sure you have installed the latest Windows patches (especially KB2789397). diff -r b4dd4c16c12d -r d89d586b26ae docs/usage/vcs_support.rst --- a/docs/usage/vcs_support.rst Wed Dec 21 14:56:09 2016 +0000 +++ b/docs/usage/vcs_support.rst Sat Dec 24 00:34:38 2016 +0100 @@ -23,7 +23,7 @@ Web server with chunked encoding -```````````````````````````````` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Large Git pushes require an HTTP server with support for chunked encoding for POST. The Python web servers waitress_ and @@ -51,7 +51,7 @@ Working with Mercurial subrepositories -`````````````````````````````````````` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This section explains how to use Mercurial subrepositories_ in Kallithea. diff -r b4dd4c16c12d -r d89d586b26ae init.d/celeryd-upstart.conf --- a/init.d/celeryd-upstart.conf Wed Dec 21 14:56:09 2016 +0000 +++ b/init.d/celeryd-upstart.conf Sat Dec 24 00:34:38 2016 +0100 @@ -2,8 +2,8 @@ # Change variables/paths as necessary and place file /etc/init/celeryd.conf # start/stop/restart as normal upstart job (ie: $ start celeryd) -description "Celery for Kallithea Mercurial Server" -author "Matt Zuba /dev/null + [ -n "$PID" ] && kill $PID &>/dev/null if [ $? = 0 ]; then rm_daemon $DAEMON stat_done @@ -67,4 +67,4 @@ ;; *) echo "usage: $0 {start|stop|restart|status}" -esac \ No newline at end of file +esac diff -r b4dd4c16c12d -r d89d586b26ae init.d/kallithea-daemon-debian --- a/init.d/kallithea-daemon-debian Wed Dec 21 14:56:09 2016 +0000 +++ b/init.d/kallithea-daemon-debian Sat Dec 24 00:34:38 2016 +0100 @@ -2,9 +2,9 @@ ######################################## #### THIS IS A DEBIAN INIT.D SCRIPT #### ######################################## - + ### BEGIN INIT INFO -# Provides: kallithea +# Provides: kallithea # Required-Start: $all # Required-Stop: $all # Default-Start: 2 3 4 5 @@ -12,29 +12,29 @@ # Short-Description: starts instance of kallithea # Description: starts instance of kallithea using start-stop-daemon ### END INIT INFO - + APP_NAME="kallithea" APP_HOMEDIR="opt" APP_PATH="/$APP_HOMEDIR/$APP_NAME" - + CONF_NAME="production.ini" - + PID_PATH="$APP_PATH/$APP_NAME.pid" LOG_PATH="$APP_PATH/$APP_NAME.log" - + PYTHON_PATH="/$APP_HOMEDIR/$APP_NAME-venv" - + RUN_AS="root" - + DAEMON="$PYTHON_PATH/bin/paster" - + DAEMON_OPTS="serve --daemon \ --user=$RUN_AS \ --group=$RUN_AS \ --pid-file=$PID_PATH \ --log-file=$LOG_PATH $APP_PATH/$CONF_NAME" - - + + start() { echo "Starting $APP_NAME" PYTHON_EGG_CACHE="/tmp" start-stop-daemon -d $APP_PATH \ @@ -43,18 +43,18 @@ --user $RUN_AS \ --exec $DAEMON -- $DAEMON_OPTS } - + stop() { echo "Stopping $APP_NAME" start-stop-daemon -d $APP_PATH \ --stop --quiet \ --pidfile $PID_PATH || echo "$APP_NAME - Not running!" - + if [ -f $PID_PATH ]; then rm $PID_PATH fi } - + status() { echo -n "Checking status of $APP_NAME ... " pid=`cat $PID_PATH` @@ -65,7 +65,7 @@ echo "NOT running" fi } - + case "$1" in status) status @@ -87,4 +87,4 @@ *) echo "Usage: $0 {start|stop|restart}" exit 1 -esac \ No newline at end of file +esac diff -r b4dd4c16c12d -r d89d586b26ae init.d/kallithea-daemon-gentoo --- a/init.d/kallithea-daemon-gentoo Wed Dec 21 14:56:09 2016 +0000 +++ b/init.d/kallithea-daemon-gentoo Sat Dec 24 00:34:38 2016 +0100 @@ -56,6 +56,6 @@ #stop() echo "sleep3" sleep 3 - + #start() } diff -r b4dd4c16c12d -r d89d586b26ae init.d/kallithea-daemon-redhat --- a/init.d/kallithea-daemon-redhat Wed Dec 21 14:56:09 2016 +0000 +++ b/init.d/kallithea-daemon-redhat Sat Dec 24 00:34:38 2016 +0100 @@ -129,4 +129,4 @@ ;; esac -exit $RETVAL \ No newline at end of file +exit $RETVAL diff -r b4dd4c16c12d -r d89d586b26ae init.d/kallithea-upstart.conf --- a/init.d/kallithea-upstart.conf Wed Dec 21 14:56:09 2016 +0000 +++ b/init.d/kallithea-upstart.conf Sat Dec 24 00:34:38 2016 +0100 @@ -2,8 +2,8 @@ # Change variables/paths as necessary and place file /etc/init/kallithea.conf # start/stop/restart as normal upstart job (ie: $ start kallithea) -description "Kallithea Mercurial Server" -author "Matt Zuba kallithea/brand.py - -BRAND = "kallithea" try: - from kallithea.brand import BRAND + import kallithea.brand except ImportError: pass - -# Prefix for the ui and settings table names -DB_PREFIX = (BRAND + "_") if BRAND != "kallithea" else "" - -# Users.extern_type and .extern_name value for local users -EXTERN_TYPE_INTERNAL = BRAND if BRAND != 'kallithea' else 'internal' - -# db_migrate_version.repository_id value, same as kallithea/lib/dbmigrate/migrate.cfg -DB_MIGRATIONS = BRAND + "_db_migrations" +else: + assert False, 'Database rebranding is no longer supported; see README.' -try: - from kallithea.lib import get_current_revision - _rev = get_current_revision(quiet=True) - if _rev and len(VERSION) > 3: - VERSION += (_rev[0],) -except ImportError: - pass -__version__ = ('.'.join((str(each) for each in VERSION[:3]))) -__dbversion__ = 31 # defines current db version for migrations +__version__ = '.'.join(str(each) for each in VERSION) __platform__ = platform.system() __license__ = 'GPLv3' __py_version__ = sys.version_info @@ -85,17 +61,3 @@ is_windows = __platform__ in ['Windows'] is_unix = not is_windows - -if len(VERSION) > 3: - __version__ += '.'+VERSION[3] - - if len(VERSION) > 4: - __version__ += VERSION[4] - else: - __version__ += '0' - -# Hack for making the celery dependency kombu==1.5.1 compatible with Python -# 2.7.11 which has https://hg.python.org/releases/2.7.11/rev/24bdc4940e81 -import uuid -if not hasattr(uuid, '_uuid_generate_random'): - uuid._uuid_generate_random = None diff -r b4dd4c16c12d -r d89d586b26ae kallithea/alembic/env.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/alembic/env.py Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Alembic migration environment (configuration). + +import logging +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool + +from kallithea.model import db + + +# The alembic.config.Config object, which wraps the current .ini file. +config = context.config + +# Default to use the main Kallithea database string in [app:main]. +# For advanced uses, this can be overridden by specifying an explicit +# [alembic] sqlalchemy.url. +database_url = ( + config.get_main_option('sqlalchemy.url') or + config.get_section_option('app:main', 'sqlalchemy.url') +) + +# Configure default logging for Alembic. (This can be overriden by the +# config file, but usually isn't.) +logging.getLogger('alembic').setLevel(logging.INFO) + +# Setup Python loggers based on the config file provided to the alembic +# command. If we're being invoked via the Alembic API (presumably for +# stamping during "paster setup-db"), config_file_name is not available, +# and loggers are assumed to already have been configured. +if config.config_file_name: + fileConfig(config.config_file_name, disable_existing_loggers=False) + + +def include_in_autogeneration(object, name, type, reflected, compare_to): + """Filter changes subject to autogeneration of migrations. """ + + # Don't include changes to sqlite_sequence. + if type == 'table' and name == 'sqlite_sequence': + return False + + return True + + +def run_migrations_offline(): + """Run migrations in 'offline' (--sql) mode. + + This produces an SQL script instead of directly applying the changes. + Some migrations may not run in offline mode. + """ + context.configure( + url=database_url, + literal_binds=True, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + Connects to the database and directly applies the necessary + migrations. + """ + cfg = config.get_section(config.config_ini_section) + cfg['sqlalchemy.url'] = database_url + connectable = engine_from_config( + cfg, + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + with connectable.connect() as connection: + context.configure( + connection=connection, + + # Support autogeneration of migration scripts based on "diff" between + # current database schema and kallithea.model.db schema. + target_metadata=db.Base.metadata, + include_object=include_in_autogeneration, + render_as_batch=True, # batch mode is needed for SQLite support + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/alembic/script.py.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/alembic/script.py.mako Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +## Template for creating new Alembic migration scripts. +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" + +# The following opaque hexadecimal identifiers ("revisions") are used +# by Alembic to track this migration script and its relations to others. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff -r b4dd4c16c12d -r d89d586b26ae kallithea/alembic/versions/9358dc3d6828_drop_sqlalchemy_migrate_support.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/alembic/versions/9358dc3d6828_drop_sqlalchemy_migrate_support.py Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,37 @@ +# 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 . + +"""Drop SQLAlchemy Migrate support + +Revision ID: 9358dc3d6828 +Revises: +Create Date: 2016-03-01 15:21:30.896585 + +""" + +# The following opaque hexadecimal identifiers ("revisions") are used +# by Alembic to track this migration script and its relations to others. +revision = '9358dc3d6828' +down_revision = None +branch_labels = None +depends_on = None + +from alembic import op + + +def upgrade(): + op.drop_table('db_migrate_version') + + +def downgrade(): + raise NotImplementedError('cannot revert to SQLAlchemy Migrate') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/bin/kallithea_backup.py --- a/kallithea/bin/kallithea_backup.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/bin/kallithea_backup.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,11 +28,11 @@ import os import sys - import logging import tarfile import datetime import subprocess +import tempfile logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)-5.5s %(message)s") @@ -47,7 +47,7 @@ self.repos_path = self.get_repos_path(repos_location) self.backup_server = backup_server - self.backup_file_path = '/tmp' + self.backup_file_path = tempfile.gettempdir() logging.info('starting backup for %s', self.repos_path) logging.info('backup target %s', self.backup_file_path) @@ -86,7 +86,7 @@ '%(backup_server)s' % params] subprocess.call(cmd) - logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4]) + logging.info('Transferred file %s to %s', self.backup_file_name, cmd[4]) def rm_file(self): logging.info('Removing file %s', self.backup_file_name) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/bin/kallithea_config.py --- a/kallithea/bin/kallithea_config.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/bin/kallithea_config.py Sat Dec 24 00:34:38 2016 +0100 @@ -84,7 +84,7 @@ # recurse because there may be more escaped separators endlist = _escape_split(after, sep) - # finish building the escaped value. we use endlist[0] becaue the first + # finish building the escaped value. we use endlist[0] because the first # part of the string sent in recursion is the rest of the escaped value. unfinished += sep + endlist[0] diff -r b4dd4c16c12d -r d89d586b26ae kallithea/bin/ldap_sync.conf --- a/kallithea/bin/ldap_sync.conf Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/bin/ldap_sync.conf Sat Dec 24 00:34:38 2016 +0100 @@ -8,4 +8,4 @@ ldap_key = XXXXXXXXX base_dn = dc=example,dc=com -sync_users = True \ No newline at end of file +sync_users = True diff -r b4dd4c16c12d -r d89d586b26ae kallithea/bin/rebranddb.py --- a/kallithea/bin/rebranddb.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -#!/usr/bin/env python2 - -# 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 . -""" -Script for rebranding of database to and from what Kallithea expects - -Works on databases from v1.7.2 to v2.2.5 -""" - -import sys -from sqlalchemy import * -import sqlalchemy.orm -import sqlalchemy.ext.declarative -import migrate.changeset # a part of sqlalchemy-migrate which is available on pypi - -def do_migrate(db, old, new): - print 'Migrating %s from %s to %s' % (db, old or '?', new) - metadata = MetaData() - metadata.bind = create_engine(db) - metadata.reflect() - assert metadata.tables, 'Cannot reflect table names from db' - - if not old: - assert 'db_migrate_version' in metadata.tables, 'Cannot reflect db_migrate_version from db' - t = metadata.tables['db_migrate_version'] - l = t.select().where(t.c.repository_path == 'versions').execute().fetchall() - assert len(l) == 1, 'Cannot find a single versions entry in db_migrate_version' - assert l[0].repository_id.endswith('_db_migrations') - old = l[0].repository_id[:-len('_db_migrations')] - print 'Detected migration from old name %s' % old - if new != old: - assert not t.select().where(t.c.repository_id == new + '_db_migrations').execute().fetchall(), 'db_migrate_version has entries for both old and new name' - - def tablename(brand, s): - return s if brand == 'kallithea' else (brand + '_' + s) - new_ui_name = tablename(new, 'ui') - old_ui_name = tablename(old, 'ui') - new_settings_name = tablename(new, 'settings') - old_settings_name = tablename(old, 'settings') - - # Table renames using sqlalchemy-migrate (available on pypi) - if new_ui_name == old_ui_name: - print 'No renaming of %s' % new_ui_name - else: - try: - t = metadata.tables[old_ui_name] - print 'Renaming', t, 'to', new_ui_name - migrate.changeset.rename_table(t, new_ui_name) - except KeyError as e: - print 'Not renaming ui:', e - - if new_settings_name == old_settings_name: - print 'No renaming of %s' % new_settings_name - else: - try: - t = metadata.tables[old_settings_name] - print 'Renaming', t, 'to', new_settings_name - migrate.changeset.rename_table(t, new_settings_name) - except KeyError as e: - print 'Not renaming settings:', e - - old_auth_name = 'internal' if old == 'kallithea' else old - new_auth_name = 'internal' if new == 'kallithea' else new - - # using this API because ... dunno ... it is simple and works - conn = metadata.bind.connect() - trans = conn.begin() - t = metadata.tables['users'] - - print 'Bulk fixing of User extern_name' - try: - t.c.extern_name - except AttributeError: - print 'No extern_name to rename' - else: - t.update().where(t.c.extern_name == old_auth_name).values(extern_name=new_auth_name).execute() - - print 'Bulk fixing of User extern_type' - try: - t.c.extern_type - except AttributeError: - print 'No extern_type to rename' - else: - t.update().where(t.c.extern_type == old_auth_name).values(extern_type=new_auth_name).execute() - - trans.commit() - - # For the following conversions, use ORM ... and create stub models that works for that purpose - Base = sqlalchemy.ext.declarative.declarative_base() - - class Ui(Base): - __tablename__ = new_ui_name - ui_id = Column("ui_id", Integer(), primary_key=True) - ui_section = Column("ui_section", String()) - ui_key = Column("ui_key", String()) - ui_value = Column("ui_value", String()) - ui_active = Column("ui_active", Boolean()) - - class Setting(Base): - __tablename__ = new_settings_name - app_settings_id = Column("app_settings_id", Integer(), primary_key=True) - app_settings_name = Column("app_settings_name", String()) - app_settings_value = Column("app_settings_value", String()) - #app_settings_type = Column("app_settings_type", String()) # not present in v1.7.2 - - class DbMigrateVersion(Base): - __tablename__ = 'db_migrate_version' - repository_id = Column('repository_id', String(), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) - - Session = sqlalchemy.orm.sessionmaker(bind=metadata.bind) - session = Session() - - print 'Fixing hook names' - - oldhooks = u'python:%s.lib.hooks.' % old - newhooks = u'python:%s.lib.hooks.' % new - for u in session.query(Ui).filter(Ui.ui_section == 'hooks').all(): - if u.ui_value.startswith(oldhooks): - print '- fixing %s' % u.ui_key - u.ui_value = newhooks + u.ui_value[len(oldhooks):] - session.add(u) - session.commit() - - print 'Fixing auth module names' - for s in session.query(Setting).filter(Setting.app_settings_name == 'auth_plugins').all(): - print '- fixing %s' % s.app_settings_name - s.app_settings_value = (s.app_settings_value - .replace(old + '.lib.auth_modules.auth_', new + '.lib.auth_modules.auth_') - .replace('.auth_modules.auth_' + old_auth_name, '.auth_modules.auth_' + new_auth_name)) - session.add(s) - for s in session.query(Setting).filter(Setting.app_settings_name == 'auth_' + old_auth_name + '_enabled').all(): - print '- fixing %s' % s.app_settings_name - s.app_settings_name = 'auth_' + new_auth_name + '_enabled' - session.add(s) - session.commit() - - print 'Fixing db migration version number' - for s in session.query(DbMigrateVersion).filter(DbMigrateVersion.repository_id == old + '_db_migrations', DbMigrateVersion.repository_path == 'versions').all(): - print '- fixing %s' % s.repository_id - s.repository_id = new + '_db_migrations' - session.commit() - - print 'Done' - -def main(argv): - if len(argv) < 2 or argv[1] in ['-h', '--help']: - print 'usage: kallithea/bin/rebranddb.py DBSTRING [NEW] [OLD]' - print ' where DBSTRING is the value of sqlalchemy.db1.url from the .ini,' - print ' NEW defaults to "kallithea", OLD is by default detected from the db"' - raise SystemExit(0) - new = 'kallithea' - if len(argv) > 2: - new = argv[2] - old = None - if len(argv) > 3: - old = argv[3] - do_migrate(argv[1], old, new) - -if __name__ == '__main__': - main(sys.argv) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/bin/template.ini.mako --- a/kallithea/bin/template.ini.mako Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/bin/template.ini.mako Sat Dec 24 00:34:38 2016 +0100 @@ -49,26 +49,25 @@ #error_email_from = paste_error@example.com <%text>## SMTP server settings -<%text>## Only smtp_server is mandatory. All other settings take the specified default -<%text>## values. +<%text>## If specifying credentials, make sure to use secure connections. +<%text>## Default: Send unencrypted unauthenticated mails to the specified smtp_server. +<%text>## For "SSL", use smtp_use_ssl = true and smtp_port = 465. +<%text>## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587. #smtp_server = smtp.example.com #smtp_username = #smtp_password = #smtp_port = 25 +#smtp_use_ssl = false #smtp_use_tls = false -#smtp_use_ssl = false -<%text>## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.). -<%text>## If empty, use any of the authentication parameters supported by the server. -#smtp_auth = [server:main] %if http_server == 'paste': <%text>## PASTE ## use = egg:Paste#http <%text>## nr of worker threads to spawn -threadpool_workers = 5 +threadpool_workers = 1 <%text>## max request before thread respawn -threadpool_max_requests = 10 +threadpool_max_requests = 100 <%text>## option to use threads of process use_threadpool = true @@ -76,7 +75,7 @@ <%text>## WAITRESS ## use = egg:waitress#main <%text>## number of worker threads -threads = 5 +threads = 1 <%text>## MAX BODY SIZE 100GB max_request_body_size = 107374182400 <%text>## use poll instead of select, fixes fd limits, may not work on old @@ -95,7 +94,7 @@ <%text>## recommended for bigger setup is using of of other than sync one worker_class = sync max_requests = 1000 -<%text>## ammount of time a worker can handle request before it gets killed and +<%text>## amount of time a worker can handle request before it gets killed and <%text>## restarted timeout = 3600 @@ -196,9 +195,6 @@ <%text>## cut off limit for large diffs (size in bytes) cut_off_limit = 256000 -<%text>## use cache version of scm repo everywhere -vcs_full_cache = true - <%text>## force https in Kallithea, fixes https redirects, assumes it's always https force_https = false @@ -224,6 +220,11 @@ show_sha_length = 12 show_revision_number = false +<%text>## Canonical URL to use when creating full URLs in UI and texts. +<%text>## Useful when the site is available under different names or protocols. +<%text>## Defaults to what is provided in the WSGI environment. +#canonical_url = https://kallithea.example.com/repos + <%text>## gist URL alias, used to create nicer urls for gist. This should be an <%text>## url that does rewrites to _admin/gists/. <%text>## example: http://gist.example.com/{gistid}. Empty means use the internal @@ -243,7 +244,7 @@ # FilesController:archivefile <%text>## default encoding used to convert from and to unicode -<%text>## can be also a comma seperated list of encoding in case of mixed encodings +<%text>## can be also a comma separated list of encoding in case of mixed encodings default_encoding = utf8 <%text>## issue tracker for Kallithea (leave blank to disable, absent for default) @@ -278,12 +279,6 @@ #issue_server_link_wiki = https://wiki.example.com/{id} #issue_prefix_wiki = WIKI- -<%text>## instance-id prefix -<%text>## a prefix key for this instance used for cache invalidation when running -<%text>## multiple instances of kallithea, make sure it's globally unique for -<%text>## all running kallithea instances. Leave empty if you don't use it -instance_id = - <%text>## alternative return HTTP header for failed authentication. Default HTTP <%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with <%text>## handling that. Set this variable to 403 to return HTTPForbidden @@ -299,19 +294,29 @@ <%text>## allows to setup custom hooks in settings page allow_custom_hooks_settings = True +<%text>## extra extensions for indexing, space separated and without the leading '.'. +# index.extensions = +# gemfile +# lock + +<%text>## extra filenames for indexing, space separated +# index.filenames = +# .dockerignore +# .editorconfig +# INSTALL +# CHANGELOG + <%text>#################################### <%text>### CELERY CONFIG #### <%text>#################################### use_celery = false -broker.host = localhost -broker.vhost = rabbitmqhost -broker.port = 5672 -broker.user = rabbitmq -broker.password = qweqwe + +<%text>## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq: +broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost celery.imports = kallithea.lib.celerylib.tasks - +celery.accept.content = pickle celery.result.backend = amqp celery.result.dburi = amqp:// celery.result.serialier = json @@ -320,11 +325,9 @@ #celery.amqp.task.result.expires = 18000 celeryd.concurrency = 2 -#celeryd.log.file = celeryd.log -celeryd.log.level = DEBUG celeryd.max.tasks.per.child = 1 -<%text>## tasks will never be sent to the queue, but executed locally instead. +<%text>## If true, tasks will never be sent to the queue, but executed locally instead. celery.always.eager = false <%text>#################################### @@ -421,7 +424,7 @@ <%text>## (saves API quota for intensive logging) appenlight.logging_on_error = false -<%text>## list of additonal keywords that should be grabbed from environ object +<%text>## list of additional keywords that should be grabbed from environ object <%text>## can be string with comma separated list of words in lowercase <%text>## (by default client will always send following info: <%text>## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that @@ -481,22 +484,28 @@ %if database_engine == 'sqlite': # SQLITE [default] -sqlalchemy.db1.url = sqlite:///${here}/kallithea.db?timeout=60 +sqlalchemy.url = sqlite:///${here}/kallithea.db?timeout=60 %elif database_engine == 'postgres': # POSTGRESQL -sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea +sqlalchemy.url = postgresql://user:pass@localhost/kallithea %elif database_engine == 'mysql': # MySQL -sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea +sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8 %endif # see sqlalchemy docs for others -sqlalchemy.db1.echo = false -sqlalchemy.db1.pool_recycle = 3600 -sqlalchemy.db1.convert_unicode = true +sqlalchemy.echo = false +sqlalchemy.pool_recycle = 3600 + +<%text>################################ +<%text>### ALEMBIC CONFIGURATION #### +<%text>################################ + +[alembic] +script_location = kallithea:alembic <%text>################################ <%text>### LOGGING CONFIGURATION #### diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/conf.py --- a/kallithea/config/conf.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/conf.py Sat Dec 24 00:34:38 2016 +0100 @@ -25,19 +25,21 @@ :license: GPLv3, see LICENSE.md for more details. """ -from kallithea.lib.utils2 import __get_lem +from kallithea.lib import pygmentsutils # language map is also used by whoosh indexer, which for those specified # extensions will index it's content -LANGUAGES_EXTENSIONS_MAP = __get_lem() +LANGUAGES_EXTENSIONS_MAP = pygmentsutils.get_lem() + +# Whoosh index targets -#============================================================================== -# WHOOSH INDEX EXTENSIONS -#============================================================================== -# EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH +# Extensions we want to index content of using whoosh INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys() +# Filenames we want to index content of using whoosh +INDEX_FILENAMES = pygmentsutils.get_index_filenames() + # list of readme files to search in file tree and display in summary # attached weights defines the search order lower is first ALL_READMES = [ @@ -65,7 +67,3 @@ PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)] ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS - -DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" - -DATE_FORMAT = "%Y-%m-%d" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/deployment.ini_tmpl --- a/kallithea/config/deployment.ini_tmpl Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/deployment.ini_tmpl Sat Dec 24 00:34:38 2016 +0100 @@ -50,32 +50,31 @@ #error_email_from = paste_error@example.com ## SMTP server settings -## Only smtp_server is mandatory. All other settings take the specified default -## values. +## If specifying credentials, make sure to use secure connections. +## Default: Send unencrypted unauthenticated mails to the specified smtp_server. +## For "SSL", use smtp_use_ssl = true and smtp_port = 465. +## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587. #smtp_server = smtp.example.com #smtp_username = #smtp_password = #smtp_port = 25 +#smtp_use_ssl = false #smtp_use_tls = false -#smtp_use_ssl = false -## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.). -## If empty, use any of the authentication parameters supported by the server. -#smtp_auth = [server:main] ## PASTE ## #use = egg:Paste#http ## nr of worker threads to spawn -#threadpool_workers = 5 +#threadpool_workers = 1 ## max request before thread respawn -#threadpool_max_requests = 10 +#threadpool_max_requests = 100 ## option to use threads of process #use_threadpool = true ## WAITRESS ## use = egg:waitress#main ## number of worker threads -threads = 5 +threads = 1 ## MAX BODY SIZE 100GB max_request_body_size = 107374182400 ## use poll instead of select, fixes fd limits, may not work on old @@ -93,7 +92,7 @@ ## recommended for bigger setup is using of of other than sync one #worker_class = sync #max_requests = 1000 -## ammount of time a worker can handle request before it gets killed and +## amount of time a worker can handle request before it gets killed and ## restarted #timeout = 3600 @@ -192,9 +191,6 @@ ## cut off limit for large diffs (size in bytes) cut_off_limit = 256000 -## use cache version of scm repo everywhere -vcs_full_cache = true - ## force https in Kallithea, fixes https redirects, assumes it's always https force_https = false @@ -220,6 +216,11 @@ show_sha_length = 12 show_revision_number = false +## Canonical URL to use when creating full URLs in UI and texts. +## Useful when the site is available under different names or protocols. +## Defaults to what is provided in the WSGI environment. +#canonical_url = https://kallithea.example.com/repos + ## gist URL alias, used to create nicer urls for gist. This should be an ## url that does rewrites to _admin/gists/. ## example: http://gist.example.com/{gistid}. Empty means use the internal @@ -239,7 +240,7 @@ # FilesController:archivefile ## default encoding used to convert from and to unicode -## can be also a comma seperated list of encoding in case of mixed encodings +## can be also a comma separated list of encoding in case of mixed encodings default_encoding = utf8 ## issue tracker for Kallithea (leave blank to disable, absent for default) @@ -274,12 +275,6 @@ #issue_server_link_wiki = https://wiki.example.com/{id} #issue_prefix_wiki = WIKI- -## instance-id prefix -## a prefix key for this instance used for cache invalidation when running -## multiple instances of kallithea, make sure it's globally unique for -## all running kallithea instances. Leave empty if you don't use it -instance_id = - ## alternative return HTTP header for failed authentication. Default HTTP ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with ## handling that. Set this variable to 403 to return HTTPForbidden @@ -295,19 +290,29 @@ ## allows to setup custom hooks in settings page allow_custom_hooks_settings = True +## extra extensions for indexing, space separated and without the leading '.'. +# index.extensions = +# gemfile +# lock + +## extra filenames for indexing, space separated +# index.filenames = +# .dockerignore +# .editorconfig +# INSTALL +# CHANGELOG + #################################### ### CELERY CONFIG #### #################################### use_celery = false -broker.host = localhost -broker.vhost = rabbitmqhost -broker.port = 5672 -broker.user = rabbitmq -broker.password = qweqwe + +## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq: +broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost celery.imports = kallithea.lib.celerylib.tasks - +celery.accept.content = pickle celery.result.backend = amqp celery.result.dburi = amqp:// celery.result.serialier = json @@ -316,11 +321,9 @@ #celery.amqp.task.result.expires = 18000 celeryd.concurrency = 2 -#celeryd.log.file = celeryd.log -celeryd.log.level = DEBUG celeryd.max.tasks.per.child = 1 -## tasks will never be sent to the queue, but executed locally instead. +## If true, tasks will never be sent to the queue, but executed locally instead. celery.always.eager = false #################################### @@ -416,7 +419,7 @@ ## (saves API quota for intensive logging) appenlight.logging_on_error = false -## list of additonal keywords that should be grabbed from environ object +## list of additional keywords that should be grabbed from environ object ## can be string with comma separated list of words in lowercase ## (by default client will always send following info: ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that @@ -473,19 +476,25 @@ ######################################################### # SQLITE [default] -sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60 +sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60 # POSTGRESQL -#sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea +#sqlalchemy.url = postgresql://user:pass@localhost/kallithea # MySQL -#sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea +#sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8 # see sqlalchemy docs for others -sqlalchemy.db1.echo = false -sqlalchemy.db1.pool_recycle = 3600 -sqlalchemy.db1.convert_unicode = true +sqlalchemy.echo = false +sqlalchemy.pool_recycle = 3600 + +################################ +### ALEMBIC CONFIGURATION #### +################################ + +[alembic] +script_location = kallithea:alembic ################################ ### LOGGING CONFIGURATION #### diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/environment.py --- a/kallithea/config/environment.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/environment.py Sat Dec 24 00:34:38 2016 +0100 @@ -23,9 +23,7 @@ import pylons import mako.lookup import beaker - -# don't remove this import it does magic for celery -from kallithea.lib import celerypylons +import formencode import kallithea.lib.app_globals as app_globals @@ -33,17 +31,17 @@ from kallithea.lib import helpers from kallithea.lib.auth import set_available_permissions -from kallithea.lib.utils import repo2db_mapper, make_ui, set_app_settings,\ - load_rcextensions, check_git_version, set_vcs_config +from kallithea.lib.utils import repo2db_mapper, make_ui, set_app_settings, \ + load_rcextensions, check_git_version, set_vcs_config, set_indexer_config from kallithea.lib.utils2 import engine_from_config, str2bool from kallithea.lib.db_manage import DbManage -from kallithea.model import init_model +from kallithea.model.base import init_model from kallithea.model.scm import ScmModel log = logging.getLogger(__name__) -def load_environment(global_conf, app_conf, initial=False, +def load_environment(global_conf, app_conf, test_env=None, test_index=None): """ Configure the Pylons environment via the ``pylons.config`` @@ -80,7 +78,7 @@ # Create the Mako TemplateLookup, with the default auto-escaping config['pylons.app_globals'].mako_lookup = mako.lookup.TemplateLookup( directories=paths['templates'], - error_handler=pylons.error.handle_mako_error, + strict_undefined=True, module_directory=os.path.join(app_conf['cache_dir'], 'templates'), input_encoding='utf-8', default_filters=['escape'], imports=['from webhelpers.html import escape']) @@ -95,10 +93,10 @@ test_index = not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0)) if os.environ.get('TEST_DB'): # swap config if we pass enviroment variable - config['sqlalchemy.db1.url'] = os.environ.get('TEST_DB') + config['sqlalchemy.url'] = os.environ.get('TEST_DB') from kallithea.lib.utils import create_test_env, create_test_index - from kallithea.tests import TESTS_TMP_PATH + from kallithea.tests.base import TESTS_TMP_PATH #set KALLITHEA_NO_TMP_PATH=1 to disable re-creating the database and #test repos if test_env: @@ -107,18 +105,17 @@ if test_index: create_test_index(TESTS_TMP_PATH, config, True) - DbManage.check_waitress() # MULTIPLE DB configs # Setup the SQLAlchemy database engine - sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.') - init_model(sa_engine_db1) + sa_engine = engine_from_config(config, 'sqlalchemy.') + init_model(sa_engine) set_available_permissions(config) repos_path = make_ui('db').configitems('paths')[0][1] config['base_path'] = repos_path set_app_settings(config) - instance_id = kallithea.CONFIG.get('instance_id') + instance_id = kallithea.CONFIG.get('instance_id', '*') if instance_id == '*': instance_id = '%s-%s' % (platform.uname()[1], os.getpid()) kallithea.CONFIG['instance_id'] = instance_id @@ -130,6 +127,7 @@ # pylons kallithea.CONFIG.update(config) set_vcs_config(kallithea.CONFIG) + set_indexer_config(kallithea.CONFIG) #check git version check_git_version() @@ -137,4 +135,5 @@ if str2bool(config.get('initial_repo_scan', True)): repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False, install_git_hooks=False) + formencode.api.set_stdtranslation(languages=[config.get('lang')]) return config diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/middleware.py --- a/kallithea/config/middleware.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/middleware.py Sat Dec 24 00:34:38 2016 +0100 @@ -20,7 +20,6 @@ from paste.registry import RegistryManager from paste.urlparser import StaticURLParser from paste.deploy.converters import asbool -from paste.gzipper import make_gzip_middleware from pylons.middleware import ErrorHandler, StatusCodeRedirect from pylons.wsgiapp import PylonsApp @@ -59,7 +58,7 @@ app = PylonsApp(config=config) # Routing/Session/Cache Middleware - app = RoutesMiddleware(app, config['routes.map']) + app = RoutesMiddleware(app, config['routes.map'], use_method_override=False) app = SecureSessionMiddleware(app, config) # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares) @@ -105,7 +104,6 @@ # Serve static files static_app = StaticURLParser(config['pylons.paths']['static_files']) app = Cascade([static_app, app]) - app = make_gzip_middleware(app, global_conf, compress_level=1) app.config = config diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/post_receive_tmpl.py --- a/kallithea/config/post_receive_tmpl.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/post_receive_tmpl.py Sat Dec 24 00:34:38 2016 +0100 @@ -1,26 +1,21 @@ -#!/usr/bin/env python2 import os import sys -try: - import kallithea - KALLITHEA_HOOK_VER = '_TMPL_' - os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER - from kallithea.lib.hooks import handle_git_post_receive as _handler -except ImportError: - if os.environ.get('RC_DEBUG_GIT_HOOK'): - import traceback - print traceback.format_exc() - kallithea = None +# set output mode on windows to binary for stderr +# this prevents python (or the windows console) from replacing \n with \r\n +# git doesn't display remote output lines that contain \r +# and therefore without this modification git would displayes empty lines +# instead of the exception output +if sys.platform == "win32": + import msvcrt + msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) + +KALLITHEA_HOOK_VER = '_TMPL_' +os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER +from kallithea.lib.hooks import handle_git_post_receive as _handler def main(): - if kallithea is None: - # exit with success if we cannot import kallithea !! - # this allows simply push to this repo even without - # kallithea - sys.exit(0) - repo_path = os.path.abspath('.') push_data = sys.stdin.readlines() # os.environ is modified here by a subprocess call that diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/pre_receive_tmpl.py --- a/kallithea/config/pre_receive_tmpl.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/pre_receive_tmpl.py Sat Dec 24 00:34:38 2016 +0100 @@ -1,26 +1,21 @@ -#!/usr/bin/env python2 import os import sys -try: - import kallithea - KALLITHEA_HOOK_VER = '_TMPL_' - os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER - from kallithea.lib.hooks import handle_git_pre_receive as _handler -except ImportError: - if os.environ.get('RC_DEBUG_GIT_HOOK'): - import traceback - print traceback.format_exc() - kallithea = None +# set output mode on windows to binary for stderr +# this prevents python (or the windows console) from replacing \n with \r\n +# git doesn't display remote output lines that contain \r +# and therefore without this modification git would displayes empty lines +# instead of the exception output +if sys.platform == "win32": + import msvcrt + msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) + +KALLITHEA_HOOK_VER = '_TMPL_' +os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER +from kallithea.lib.hooks import handle_git_pre_receive as _handler def main(): - if kallithea is None: - # exit with success if we cannot import kallithea !! - # this allows simply push to this repo even without - # kallithea - sys.exit(0) - repo_path = os.path.abspath('.') push_data = sys.stdin.readlines() # os.environ is modified here by a subprocess call that diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/rcextensions/__init__.py --- a/kallithea/config/rcextensions/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/rcextensions/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -41,7 +41,7 @@ :param created_on: :param enable_downloads: :param repo_id: - :param user_id: + :param owner_id: :param enable_statistics: :param clone_uri: :param fork_id: @@ -123,7 +123,7 @@ :param created_on: :param enable_downloads: :param repo_id: - :param user_id: + :param owner_id: :param enable_statistics: :param clone_uri: :param fork_id: diff -r b4dd4c16c12d -r d89d586b26ae kallithea/config/routing.py --- a/kallithea/config/routing.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/config/routing.py Sat Dec 24 00:34:38 2016 +0100 @@ -19,6 +19,7 @@ refer to the routes manual at http://routes.groovie.org/docs/ """ +from pylons import request from routes import Mapper # prefix for non repository related links needs to be prefixed with `/` @@ -119,12 +120,11 @@ action="index", conditions=dict(method=["GET"])) m.connect("new_repo", "/create_repository", action="create_repository", conditions=dict(method=["GET"])) - m.connect("put_repo", "/repos/{repo_name:.*?}", - action="update", conditions=dict(method=["PUT"], + m.connect("update_repo", "/repos/{repo_name:.*?}", + action="update", conditions=dict(method=["POST"], function=check_repo)) - m.connect("delete_repo", "/repos/{repo_name:.*?}", - action="delete", conditions=dict(method=["DELETE"], - )) + m.connect("delete_repo", "/repos/{repo_name:.*?}/delete", + action="delete", conditions=dict(method=["POST"])) #ADMIN REPOSITORY GROUPS ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, @@ -136,7 +136,7 @@ m.connect("new_repos_group", "/repo_groups/new", action="new", conditions=dict(method=["GET"])) m.connect("update_repos_group", "/repo_groups/{group_name:.*?}", - action="update", conditions=dict(method=["PUT"], + action="update", conditions=dict(method=["POST"], function=check_group)) m.connect("repos_group", "/repo_groups/{group_name:.*?}", @@ -147,36 +147,30 @@ m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit", action="edit", conditions=dict(method=["GET"], function=check_group)) - m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit", - action="edit", - conditions=dict(method=["PUT"], function=check_group)) m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced", action="edit_repo_group_advanced", conditions=dict(method=["GET"], function=check_group)) - m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced", - action="edit_repo_group_advanced", - conditions=dict(method=["PUT"], function=check_group)) m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions", action="edit_repo_group_perms", conditions=dict(method=["GET"], function=check_group)) - m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions", + m.connect("edit_repo_group_perms_update", "/repo_groups/{group_name:.*?}/edit/permissions", action="update_perms", - conditions=dict(method=["PUT"], function=check_group)) - m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions", + conditions=dict(method=["POST"], function=check_group)) + m.connect("edit_repo_group_perms_delete", "/repo_groups/{group_name:.*?}/edit/permissions/delete", action="delete_perms", - conditions=dict(method=["DELETE"], function=check_group)) + conditions=dict(method=["POST"], function=check_group)) - m.connect("delete_repo_group", "/repo_groups/{group_name:.*?}", - action="delete", conditions=dict(method=["DELETE"], + m.connect("delete_repo_group", "/repo_groups/{group_name:.*?}/delete", + action="delete", conditions=dict(method=["POST"], function=check_group_skip_path)) #ADMIN USER ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/users') as m: - m.connect("users", "/users", + m.connect("new_user", "/users/new", action="create", conditions=dict(method=["POST"])) m.connect("users", "/users", action="index", conditions=dict(method=["GET"])) @@ -185,45 +179,41 @@ m.connect("new_user", "/users/new", action="new", conditions=dict(method=["GET"])) m.connect("update_user", "/users/{id}", - action="update", conditions=dict(method=["PUT"])) - m.connect("delete_user", "/users/{id}", - action="delete", conditions=dict(method=["DELETE"])) + action="update", conditions=dict(method=["POST"])) + m.connect("delete_user", "/users/{id}/delete", + action="delete", conditions=dict(method=["POST"])) m.connect("edit_user", "/users/{id}/edit", action="edit", conditions=dict(method=["GET"])) - m.connect("user", "/users/{id}", - action="show", conditions=dict(method=["GET"])) #EXTRAS USER ROUTES m.connect("edit_user_advanced", "/users/{id}/edit/advanced", action="edit_advanced", conditions=dict(method=["GET"])) - m.connect("edit_user_advanced", "/users/{id}/edit/advanced", - action="update_advanced", conditions=dict(method=["PUT"])) m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys", action="edit_api_keys", conditions=dict(method=["GET"])) - m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys", + m.connect("edit_user_api_keys_update", "/users/{id}/edit/api_keys", action="add_api_key", conditions=dict(method=["POST"])) - m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys", - action="delete_api_key", conditions=dict(method=["DELETE"])) + m.connect("edit_user_api_keys_delete", "/users/{id}/edit/api_keys/delete", + action="delete_api_key", conditions=dict(method=["POST"])) m.connect("edit_user_perms", "/users/{id}/edit/permissions", action="edit_perms", conditions=dict(method=["GET"])) - m.connect("edit_user_perms", "/users/{id}/edit/permissions", - action="update_perms", conditions=dict(method=["PUT"])) + m.connect("edit_user_perms_update", "/users/{id}/edit/permissions", + action="update_perms", conditions=dict(method=["POST"])) m.connect("edit_user_emails", "/users/{id}/edit/emails", action="edit_emails", conditions=dict(method=["GET"])) - m.connect("edit_user_emails", "/users/{id}/edit/emails", - action="add_email", conditions=dict(method=["PUT"])) - m.connect("edit_user_emails", "/users/{id}/edit/emails", - action="delete_email", conditions=dict(method=["DELETE"])) + m.connect("edit_user_emails_update", "/users/{id}/edit/emails", + action="add_email", conditions=dict(method=["POST"])) + m.connect("edit_user_emails_delete", "/users/{id}/edit/emails/delete", + action="delete_email", conditions=dict(method=["POST"])) m.connect("edit_user_ips", "/users/{id}/edit/ips", action="edit_ips", conditions=dict(method=["GET"])) - m.connect("edit_user_ips", "/users/{id}/edit/ips", - action="add_ip", conditions=dict(method=["PUT"])) - m.connect("edit_user_ips", "/users/{id}/edit/ips", - action="delete_ip", conditions=dict(method=["DELETE"])) + m.connect("edit_user_ips_update", "/users/{id}/edit/ips", + action="add_ip", conditions=dict(method=["POST"])) + m.connect("edit_user_ips_delete", "/users/{id}/edit/ips/delete", + action="delete_ip", conditions=dict(method=["POST"])) #ADMIN USER GROUPS REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, @@ -235,28 +225,26 @@ m.connect("new_users_group", "/user_groups/new", action="new", conditions=dict(method=["GET"])) m.connect("update_users_group", "/user_groups/{id}", - action="update", conditions=dict(method=["PUT"])) - m.connect("delete_users_group", "/user_groups/{id}", - action="delete", conditions=dict(method=["DELETE"])) + action="update", conditions=dict(method=["POST"])) + m.connect("delete_users_group", "/user_groups/{id}/delete", + action="delete", conditions=dict(method=["POST"])) m.connect("edit_users_group", "/user_groups/{id}/edit", action="edit", conditions=dict(method=["GET"]), function=check_user_group) - m.connect("users_group", "/user_groups/{id}", - action="show", conditions=dict(method=["GET"])) #EXTRAS USER GROUP ROUTES m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms", action="edit_default_perms", conditions=dict(method=["GET"])) - m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms", - action="update_default_perms", conditions=dict(method=["PUT"])) + m.connect("edit_user_group_default_perms_update", "/user_groups/{id}/edit/default_perms", + action="update_default_perms", conditions=dict(method=["POST"])) m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms", action="edit_perms", conditions=dict(method=["GET"])) - m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms", - action="update_perms", conditions=dict(method=["PUT"])) - m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms", - action="delete_perms", conditions=dict(method=["DELETE"])) + m.connect("edit_user_group_perms_update", "/user_groups/{id}/edit/perms", + action="update_perms", conditions=dict(method=["POST"])) + m.connect("edit_user_group_perms_delete", "/user_groups/{id}/edit/perms/delete", + action="delete_perms", conditions=dict(method=["POST"])) m.connect("edit_user_group_advanced", "/user_groups/{id}/edit/advanced", action="edit_advanced", conditions=dict(method=["GET"])) @@ -275,19 +263,19 @@ action="permission_globals", conditions=dict(method=["GET"])) m.connect("admin_permissions_ips", "/permissions/ips", - action="permission_ips", conditions=dict(method=["POST"])) - m.connect("admin_permissions_ips", "/permissions/ips", action="permission_ips", conditions=dict(method=["GET"])) m.connect("admin_permissions_perms", "/permissions/perms", - action="permission_perms", conditions=dict(method=["POST"])) - m.connect("admin_permissions_perms", "/permissions/perms", action="permission_perms", conditions=dict(method=["GET"])) - #ADMIN DEFAULTS REST ROUTES - rmap.resource('default', 'defaults', - controller='admin/defaults', path_prefix=ADMIN_PREFIX) + #ADMIN DEFAULTS ROUTES + with rmap.submapper(path_prefix=ADMIN_PREFIX, + controller='admin/defaults') as m: + m.connect('defaults', 'defaults', + action="index") + m.connect('defaults_update', 'defaults/{id}/update', + action="update", conditions=dict(method=["POST"])) #ADMIN AUTH SETTINGS rmap.connect('auth_settings', '%s/auth' % ADMIN_PREFIX, @@ -326,8 +314,8 @@ m.connect("admin_settings_hooks", "/settings/hooks", action="settings_hooks", conditions=dict(method=["POST"])) - m.connect("admin_settings_hooks", "/settings/hooks", - action="settings_hooks", conditions=dict(method=["DELETE"])) + m.connect("admin_settings_hooks_delete", "/settings/hooks/delete", + action="settings_hooks", conditions=dict(method=["POST"])) m.connect("admin_settings_hooks", "/settings/hooks", action="settings_hooks", conditions=dict(method=["GET"])) @@ -370,40 +358,29 @@ action="my_account_emails", conditions=dict(method=["GET"])) m.connect("my_account_emails", "/my_account/emails", action="my_account_emails_add", conditions=dict(method=["POST"])) - m.connect("my_account_emails", "/my_account/emails", - action="my_account_emails_delete", conditions=dict(method=["DELETE"])) + m.connect("my_account_emails_delete", "/my_account/emails/delete", + action="my_account_emails_delete", conditions=dict(method=["POST"])) m.connect("my_account_api_keys", "/my_account/api_keys", action="my_account_api_keys", conditions=dict(method=["GET"])) m.connect("my_account_api_keys", "/my_account/api_keys", action="my_account_api_keys_add", conditions=dict(method=["POST"])) - m.connect("my_account_api_keys", "/my_account/api_keys", - action="my_account_api_keys_delete", conditions=dict(method=["DELETE"])) + m.connect("my_account_api_keys_delete", "/my_account/api_keys/delete", + action="my_account_api_keys_delete", conditions=dict(method=["POST"])) #NOTIFICATION REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/notifications') as m: m.connect("notifications", "/notifications", - action="create", conditions=dict(method=["POST"])) - m.connect("notifications", "/notifications", action="index", conditions=dict(method=["GET"])) m.connect("notifications_mark_all_read", "/notifications/mark_all_read", action="mark_all_read", conditions=dict(method=["GET"])) m.connect("formatted_notifications", "/notifications.{format}", action="index", conditions=dict(method=["GET"])) - m.connect("new_notification", "/notifications/new", - action="new", conditions=dict(method=["GET"])) - m.connect("formatted_new_notification", "/notifications/new.{format}", - action="new", conditions=dict(method=["GET"])) - m.connect("/notifications/{notification_id}", - action="update", conditions=dict(method=["PUT"])) - m.connect("/notifications/{notification_id}", - action="delete", conditions=dict(method=["DELETE"])) - m.connect("edit_notification", "/notifications/{notification_id}/edit", - action="edit", conditions=dict(method=["GET"])) - m.connect("formatted_edit_notification", - "/notifications/{notification_id}.{format}/edit", - action="edit", conditions=dict(method=["GET"])) + m.connect("notification_update", "/notifications/{notification_id}/update", + action="update", conditions=dict(method=["POST"])) + m.connect("notification_delete", "/notifications/{notification_id}/delete", + action="delete", conditions=dict(method=["POST"])) m.connect("notification", "/notifications/{notification_id}", action="show", conditions=dict(method=["GET"])) m.connect("formatted_notification", "/notifications/{notification_id}.{format}", @@ -420,10 +397,8 @@ action="new", conditions=dict(method=["GET"])) - m.connect("/gists/{gist_id}", - action="update", conditions=dict(method=["PUT"])) - m.connect("/gists/{gist_id}", - action="delete", conditions=dict(method=["DELETE"])) + m.connect("gist_delete", "/gists/{gist_id}/delete", + action="delete", conditions=dict(method=["POST"])) m.connect("edit_gist", "/gists/{gist_id}/edit", action="edit", conditions=dict(method=["GET", "POST"])) m.connect("edit_gist_check_revision", "/gists/{gist_id}/edit/check_revision", @@ -543,8 +518,6 @@ controller='summary', action='repo_size', conditions=dict(function=check_repo)) - rmap.connect('branch_tag_switcher', '/{repo_name:.*?}/branches-tags', - controller='home', action='branch_tag_switcher') rmap.connect('repo_refs_data', '/{repo_name:.*?}/refs-data', controller='home', action='repo_refs_data') @@ -568,20 +541,20 @@ conditions=dict(method=["GET"], function=check_repo)) rmap.connect("edit_repo_perms_update", "/{repo_name:.*?}/settings/permissions", controller='admin/repos', action="edit_permissions_update", - conditions=dict(method=["PUT"], function=check_repo)) - rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions", + conditions=dict(method=["POST"], function=check_repo)) + rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions/delete", controller='admin/repos', action="edit_permissions_revoke", - conditions=dict(method=["DELETE"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) rmap.connect("edit_repo_fields", "/{repo_name:.*?}/settings/fields", controller='admin/repos', action="edit_fields", conditions=dict(method=["GET"], function=check_repo)) rmap.connect('create_repo_fields', "/{repo_name:.*?}/settings/fields/new", controller='admin/repos', action="create_repo_field", - conditions=dict(method=["PUT"], function=check_repo)) - rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}", + conditions=dict(method=["POST"], function=check_repo)) + rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}/delete", controller='admin/repos', action="delete_repo_field", - conditions=dict(method=["DELETE"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) rmap.connect("edit_repo_advanced", "/{repo_name:.*?}/settings/advanced", @@ -590,41 +563,41 @@ rmap.connect("edit_repo_advanced_locking", "/{repo_name:.*?}/settings/advanced/locking", controller='admin/repos', action="edit_advanced_locking", - conditions=dict(method=["PUT"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) rmap.connect('toggle_locking', "/{repo_name:.*?}/settings/advanced/locking_toggle", controller='admin/repos', action="toggle_locking", conditions=dict(method=["GET"], function=check_repo)) rmap.connect("edit_repo_advanced_journal", "/{repo_name:.*?}/settings/advanced/journal", controller='admin/repos', action="edit_advanced_journal", - conditions=dict(method=["PUT"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) rmap.connect("edit_repo_advanced_fork", "/{repo_name:.*?}/settings/advanced/fork", controller='admin/repos', action="edit_advanced_fork", - conditions=dict(method=["PUT"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches", controller='admin/repos', action="edit_caches", conditions=dict(method=["GET"], function=check_repo)) - rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches", + rmap.connect("update_repo_caches", "/{repo_name:.*?}/settings/caches", controller='admin/repos', action="edit_caches", - conditions=dict(method=["PUT"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote", controller='admin/repos', action="edit_remote", conditions=dict(method=["GET"], function=check_repo)) - rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote", + rmap.connect("edit_repo_remote_update", "/{repo_name:.*?}/settings/remote", controller='admin/repos', action="edit_remote", - conditions=dict(method=["PUT"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics", controller='admin/repos', action="edit_statistics", conditions=dict(method=["GET"], function=check_repo)) - rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics", + rmap.connect("edit_repo_statistics_update", "/{repo_name:.*?}/settings/statistics", controller='admin/repos', action="edit_statistics", - conditions=dict(method=["PUT"], function=check_repo)) + conditions=dict(method=["POST"], function=check_repo)) #still working url for backward compat. rmap.connect('raw_changeset_home_depraced', @@ -653,16 +626,11 @@ controller='changeset', revision='tip', action='comment', conditions=dict(function=check_repo)) - rmap.connect('changeset_comment_preview', - '/{repo_name:.*?}/changeset-comment-preview', - controller='changeset', action='preview_comment', + rmap.connect('changeset_comment_delete', + '/{repo_name:.*?}/changeset-comment/{comment_id}/delete', + controller='changeset', action='delete_comment', conditions=dict(function=check_repo, method=["POST"])) - rmap.connect('changeset_comment_delete', - '/{repo_name:.*?}/changeset-comment-delete/{comment_id}', - controller='changeset', action='delete_comment', - conditions=dict(function=check_repo, method=["DELETE"])) - rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}', controller='changeset', action='changeset_info') @@ -706,10 +674,10 @@ action='post', conditions=dict(function=check_repo, method=["POST"])) rmap.connect('pullrequest_delete', - '/{repo_name:.*?}/pull-request/{pull_request_id}', + '/{repo_name:.*?}/pull-request/{pull_request_id}/delete', controller='pullrequests', action='delete', conditions=dict(function=check_repo, - method=["DELETE"])) + method=["POST"])) rmap.connect('pullrequest_show_all', '/{repo_name:.*?}/pull-request', @@ -731,20 +699,11 @@ 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"])) + conditions=dict(function=check_repo, method=["POST"])) rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary', controller='summary', conditions=dict(function=check_repo)) - rmap.connect('branches_home', '/{repo_name:.*?}/branches', - controller='branches', conditions=dict(function=check_repo)) - - rmap.connect('tags_home', '/{repo_name:.*?}/tags', - controller='tags', conditions=dict(function=check_repo)) - - rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks', - controller='bookmarks', conditions=dict(function=check_repo)) - rmap.connect('changelog_home', '/{repo_name:.*?}/changelog', controller='changelog', conditions=dict(function=check_repo)) @@ -842,3 +801,27 @@ conditions=dict(function=check_repo)) return rmap + + +class UrlGenerator(object): + """Emulate pylons.url in providing a wrapper around routes.url + + This code was added during migration from Pylons to Turbogears2. Pylons + already provided a wrapper like this, but Turbogears2 does not. + + When the routing of Kallithea is changed to use less Routes and more + Turbogears2-style routing, this class may disappear or change. + + url() (the __call__ method) returns the URL based on a route name and + arguments. + url.current() returns the URL of the current page with arguments applied. + + Refer to documentation of Routes for details: + https://routes.readthedocs.io/en/latest/generating.html#generation + """ + def __call__(self, *args, **kwargs): + return request.environ['routes.url'](*args, **kwargs) + def current(self, *args, **kwargs): + return request.environ['routes.url'].current(*args, **kwargs) + +url = UrlGenerator() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/admin.py --- a/kallithea/controllers/admin/admin.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/admin.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,19 +28,20 @@ import logging -from pylons import request, tmpl_context as c, url +from pylons import request, tmpl_context as c from sqlalchemy.orm import joinedload from whoosh.qparser.default import QueryParser from whoosh.qparser.dateparse import DateParserPlugin from whoosh import query from sqlalchemy.sql.expression import or_, and_, func +from kallithea.config.routing import url from kallithea.model.db import UserLog -from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator +from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator from kallithea.lib.base import BaseController, render from kallithea.lib.utils2 import safe_int, remove_prefix, remove_suffix from kallithea.lib.indexers import JOURNAL_SCHEMA -from kallithea.lib.helpers import Page +from kallithea.lib.page import Page log = logging.getLogger(__name__) @@ -123,10 +124,10 @@ def __before__(self): super(AdminController, self).__before__() - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def index(self): - users_log = UserLog.query()\ - .options(joinedload(UserLog.user))\ + users_log = UserLog.query() \ + .options(joinedload(UserLog.user)) \ .options(joinedload(UserLog.repository)) #FILTERING @@ -135,7 +136,7 @@ users_log = users_log.order_by(UserLog.action_date.desc()) - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) def url_generator(**kw): return url.current(filter=c.search_term, **kw) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/auth_settings.py --- a/kallithea/controllers/admin/auth_settings.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/auth_settings.py Sat Dec 24 00:34:38 2016 +0100 @@ -27,14 +27,15 @@ import formencode.htmlfill import traceback -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _ +from webob.exc import HTTPFound +from kallithea.config.routing import url from kallithea.lib import helpers as h from kallithea.lib.compat import formatted_json from kallithea.lib.base import BaseController, render -from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator +from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator from kallithea.lib import auth_modules from kallithea.model.forms import AuthSettingsForm from kallithea.model.db import Setting @@ -46,7 +47,7 @@ class AuthSettingsController(BaseController): @LoginRequired() - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def __before__(self): super(AuthSettingsController, self).__before__() @@ -130,7 +131,6 @@ v = ','.join(v) log.debug("%s = %s", k, str(v)) setting = Setting.create_or_update(k, v) - Session().add(setting) Session().commit() h.flash(_('Auth settings updated successfully'), category='success') @@ -146,4 +146,4 @@ h.flash(_('error occurred during update of auth settings'), category='error') - return redirect(url('auth_home')) + raise HTTPFound(location=url('auth_home')) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/defaults.py --- a/kallithea/controllers/admin/defaults.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/defaults.py Sat Dec 24 00:34:38 2016 +0100 @@ -30,12 +30,13 @@ import formencode from formencode import htmlfill -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _ +from webob.exc import HTTPFound +from kallithea.config.routing import url from kallithea.lib import helpers as h -from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator +from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator from kallithea.lib.base import BaseController, render from kallithea.model.forms import DefaultsForm from kallithea.model.meta import Session @@ -46,19 +47,13 @@ class DefaultsController(BaseController): - """REST Controller styled on the Atom Publishing Protocol""" - # To properly map this controller, ensure your config/routing.py - # file has a resource setup: - # map.resource('default', 'defaults') @LoginRequired() - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def __before__(self): super(DefaultsController, self).__before__() def index(self, format='html'): - """GET /defaults: All items in the collection""" - # url('defaults') c.backends = BACKENDS.keys() defaults = Setting.get_default_repo_settings() @@ -69,30 +64,13 @@ force_defaults=False ) - def create(self): - """POST /defaults: Create a new item""" - # url('defaults') - - def new(self, format='html'): - """GET /defaults/new: Form to create a new item""" - # url('new_default') - def update(self, id): - """PUT /defaults/id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('default', id=ID), - # method='put') - # url('default', id=ID) - _form = DefaultsForm()() try: form_result = _form.to_python(dict(request.POST)) for k, v in form_result.iteritems(): setting = Setting.create_or_update(k, v) - Session().add(setting) Session().commit() h.flash(_('Default settings updated successfully'), category='success') @@ -112,21 +90,4 @@ h.flash(_('Error occurred during update of defaults'), category='error') - return redirect(url('defaults')) - - def delete(self, id): - """DELETE /defaults/id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('default', id=ID), - # method='delete') - # url('default', id=ID) - - def show(self, id, format='html'): - """GET /defaults/id: Show a specific item""" - # url('default', id=ID) - - def edit(self, id, format='html'): - """GET /defaults/id/edit: Form to edit an existing item""" - # url('edit_default', id=ID) + raise HTTPFound(location=url('defaults')) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/gists.py --- a/kallithea/controllers/admin/gists.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/gists.py Sat Dec 24 00:34:38 2016 +0100 @@ -30,10 +30,11 @@ import traceback import formencode.htmlfill -from pylons import request, response, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, response, tmpl_context as c from pylons.i18n.translation import _ +from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden +from kallithea.config.routing import url from kallithea.model.forms import GistForm from kallithea.model.gist import GistModel from kallithea.model.meta import Session @@ -43,8 +44,7 @@ from kallithea.lib.auth import LoginRequired, NotAnonymous from kallithea.lib.utils import jsonify from kallithea.lib.utils2 import safe_int, safe_unicode, time_to_datetime -from kallithea.lib.helpers import Page -from webob.exc import HTTPNotFound, HTTPForbidden +from kallithea.lib.page import Page from sqlalchemy.sql.expression import or_ from kallithea.lib.vcs.exceptions import VCSError, NodeNotChangedError @@ -68,45 +68,41 @@ @LoginRequired() def index(self): - """GET /admin/gists: All items in the collection""" - # url('gists') - not_default_user = c.authuser.username != User.DEFAULT_USER + not_default_user = not c.authuser.is_default_user c.show_private = request.GET.get('private') and not_default_user c.show_public = request.GET.get('public') and not_default_user - gists = Gist().query()\ - .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\ + gists = Gist().query() \ + .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \ .order_by(Gist.created_on.desc()) # MY private if c.show_private and not c.show_public: - gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\ - .filter(Gist.gist_owner == c.authuser.user_id) + gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE) \ + .filter(Gist.owner_id == c.authuser.user_id) # MY public elif c.show_public and not c.show_private: - gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\ - .filter(Gist.gist_owner == c.authuser.user_id) + gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC) \ + .filter(Gist.owner_id == c.authuser.user_id) # MY public+private elif c.show_private and c.show_public: gists = gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC, - Gist.gist_type == Gist.GIST_PRIVATE))\ - .filter(Gist.gist_owner == c.authuser.user_id) + Gist.gist_type == Gist.GIST_PRIVATE)) \ + .filter(Gist.owner_id == c.authuser.user_id) # default show ALL public gists if not c.show_public and not c.show_private: gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC) c.gists = gists - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) c.gists_pager = Page(c.gists, page=p, items_per_page=10) return render('admin/gists/index.html') @LoginRequired() @NotAnonymous() def create(self): - """POST /admin/gists: Create a new item""" - # url('gists') self.__load_defaults() gist_form = GistForm([x[0] for x in c.lifetime_values])() try: @@ -144,40 +140,20 @@ except Exception as e: log.error(traceback.format_exc()) h.flash(_('Error occurred during gist creation'), category='error') - return redirect(url('new_gist')) - return redirect(url('gist', gist_id=new_gist_id)) + raise HTTPFound(location=url('new_gist')) + raise HTTPFound(location=url('gist', gist_id=new_gist_id)) @LoginRequired() @NotAnonymous() def new(self, format='html'): - """GET /admin/gists/new: Form to create a new item""" - # url('new_gist') self.__load_defaults() return render('admin/gists/new.html') @LoginRequired() @NotAnonymous() - def update(self, gist_id): - """PUT /admin/gists/gist_id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('gist', gist_id=ID), - # method='put') - # url('gist', gist_id=ID) - - @LoginRequired() - @NotAnonymous() def delete(self, gist_id): - """DELETE /admin/gists/gist_id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('gist', gist_id=ID), - # method='delete') - # url('gist', gist_id=ID) gist = GistModel().get_gist(gist_id) - owner = gist.gist_owner == c.authuser.user_id + owner = gist.owner_id == c.authuser.user_id if h.HasPermissionAny('hg.admin')() or owner: GistModel().delete(gist) Session().commit() @@ -185,12 +161,10 @@ else: raise HTTPForbidden() - return redirect(url('gists')) + raise HTTPFound(location=url('gists')) @LoginRequired() def show(self, gist_id, revision='tip', format='html', f_path=None): - """GET /admin/gists/gist_id: Show a specific item""" - # url('gist', gist_id=ID) c.gist = Gist.get_or_404(gist_id) #check if this gist is not expired @@ -214,8 +188,6 @@ @LoginRequired() @NotAnonymous() def edit(self, gist_id, format='html'): - """GET /admin/gists/gist_id/edit: Form to edit an existing item""" - # url('edit_gist', gist_id=ID) c.gist = Gist.get_or_404(gist_id) #check if this gist is not expired @@ -270,7 +242,7 @@ h.flash(_('Error occurred during update of gist %s') % gist_id, category='error') - return redirect(url('gist', gist_id=gist_id)) + raise HTTPFound(location=url('gist', gist_id=gist_id)) return rendered diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/my_account.py --- a/kallithea/controllers/admin/my_account.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/my_account.py Sat Dec 24 00:34:38 2016 +0100 @@ -31,11 +31,11 @@ from sqlalchemy import func from formencode import htmlfill -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _ +from webob.exc import HTTPFound -from kallithea import EXTERN_TYPE_INTERNAL +from kallithea.config.routing import url from kallithea.lib import helpers as h from kallithea.lib import auth_modules from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser @@ -69,22 +69,20 @@ if c.user.username == User.DEFAULT_USER: h.flash(_("You can't edit this user since it's" " crucial for entire application"), category='warning') - return redirect(url('users')) - c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL + raise HTTPFound(location=url('users')) def _load_my_repos_data(self, watched=False): if watched: admin = False - repos_list = [x.follows_repository for x in - Session().query(UserFollowing).filter( - UserFollowing.user_id == - self.authuser.user_id).all()] + repos_list = Session().query(Repository) \ + .join(UserFollowing) \ + .filter(UserFollowing.user_id == + self.authuser.user_id).all() else: admin = True - repos_list = Session().query(Repository)\ - .filter(Repository.user_id == - self.authuser.user_id)\ - .order_by(func.lower(Repository.repo_name)).all() + repos_list = Session().query(Repository) \ + .filter(Repository.owner_id == + self.authuser.user_id).all() repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, admin=admin) @@ -92,10 +90,6 @@ return json.dumps(repos_data) def my_account(self): - """ - GET /_admin/my_account Displays info about my account - """ - # url('my_account') c.active = 'profile' self.__load_data() c.perm_user = AuthUser(user_id=self.authuser.user_id) @@ -144,7 +138,7 @@ h.flash(_('Error occurred during update of user %s') \ % form_result.get('username'), category='error') if update: - return redirect('my_account') + raise HTTPFound(location='my_account') return htmlfill.render( render('admin/my_account/my_account.html'), defaults=defaults, @@ -207,7 +201,7 @@ c.active = 'emails' self.__load_data() - c.user_email_map = UserEmailMap.query()\ + c.user_email_map = UserEmailMap.query() \ .filter(UserEmailMap.user == c.user).all() return render('admin/my_account/my_account.html') @@ -225,7 +219,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during email saving'), category='error') - return redirect(url('my_account_emails')) + raise HTTPFound(location=url('my_account_emails')) def my_account_emails_delete(self): email_id = request.POST.get('del_email_id') @@ -233,7 +227,7 @@ user_model.delete_extra_email(self.authuser.user_id, email_id) Session().commit() h.flash(_("Removed email from user"), category='success') - return redirect(url('my_account_emails')) + raise HTTPFound(location=url('my_account_emails')) def my_account_api_keys(self): c.active = 'api_keys' @@ -257,21 +251,18 @@ ApiKeyModel().create(self.authuser.user_id, description, lifetime) Session().commit() h.flash(_("API key successfully created"), category='success') - return redirect(url('my_account_api_keys')) + raise HTTPFound(location=url('my_account_api_keys')) def my_account_api_keys_delete(self): api_key = request.POST.get('del_api_key') - user_id = self.authuser.user_id if request.POST.get('del_api_key_builtin'): - user = User.get(user_id) - if user is not None: - user.api_key = generate_api_key() - Session().add(user) - Session().commit() - h.flash(_("API key successfully reset"), category='success') + user = User.get(self.authuser.user_id) + user.api_key = generate_api_key() + Session().commit() + h.flash(_("API key successfully reset"), category='success') elif api_key: ApiKeyModel().delete(api_key, self.authuser.user_id) Session().commit() h.flash(_("API key successfully deleted"), category='success') - return redirect(url('my_account_api_keys')) + raise HTTPFound(location=url('my_account_api_keys')) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/notifications.py --- a/kallithea/controllers/admin/notifications.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/notifications.py Sat Dec 24 00:34:38 2016 +0100 @@ -30,8 +30,7 @@ from pylons import request from pylons import tmpl_context as c -from pylons.controllers.util import abort -from webob.exc import HTTPBadRequest +from webob.exc import HTTPBadRequest, HTTPForbidden from kallithea.model.db import Notification from kallithea.model.notification import NotificationModel @@ -39,7 +38,7 @@ from kallithea.lib.auth import LoginRequired, NotAnonymous from kallithea.lib.base import BaseController, render from kallithea.lib import helpers as h -from kallithea.lib.helpers import Page +from kallithea.lib.page import Page from kallithea.lib.utils2 import safe_int @@ -59,13 +58,11 @@ super(NotificationsController, self).__before__() def index(self, format='html'): - """GET /_admin/notifications: All items in the collection""" - # url('notifications') c.user = self.authuser - notif = NotificationModel().get_for_user(self.authuser.user_id, + notif = NotificationModel().query_for_user(self.authuser.user_id, filter_=request.GET.getall('type')) - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.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, @@ -88,30 +85,15 @@ filter_=request.GET.getall('type')) Session().commit() c.user = self.authuser - notif = nm.get_for_user(self.authuser.user_id, - filter_=request.GET.getall('type')) + notif = nm.query_for_user(self.authuser.user_id, + filter_=request.GET.getall('type')) c.notifications = Page(notif, page=1, items_per_page=10) return render('admin/notifications/notifications_data.html') - def create(self): - """POST /_admin/notifications: Create a new item""" - # url('notifications') - - def new(self, format='html'): - """GET /_admin/notifications/new: Form to create a new item""" - # url('new_notification') - def update(self, notification_id): - """PUT /_admin/notifications/id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('notification', notification_id=ID), - # method='put') - # url('notification', notification_id=ID) try: no = Notification.get(notification_id) - owner = all(un.user.user_id == c.authuser.user_id + owner = all(un.user_id == c.authuser.user_id for un in no.notifications_to_users) if h.HasPermissionAny('hg.admin')() or owner: # deletes only notification2user @@ -124,16 +106,9 @@ raise HTTPBadRequest() def delete(self, notification_id): - """DELETE /_admin/notifications/id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('notification', notification_id=ID), - # method='delete') - # url('notification', notification_id=ID) try: no = Notification.get(notification_id) - owner = any(un.user.user_id == c.authuser.user_id + owner = any(un.user_id == c.authuser.user_id for un in no.notifications_to_users) if h.HasPermissionAny('hg.admin')() or owner: # deletes only notification2user @@ -146,30 +121,20 @@ raise HTTPBadRequest() def show(self, notification_id, format='html'): - """GET /_admin/notifications/id: Show a specific item""" - # url('notification', notification_id=ID) - c.user = self.authuser - no = Notification.get(notification_id) + notification = Notification.get_or_404(notification_id) - owner = any(un.user.user_id == c.authuser.user_id - for un in no.notifications_to_users) - repo_admin = h.HasRepoPermissionAny('repository.admin') - if no and (h.HasPermissionAny('hg.admin')() or repo_admin or owner): - unotification = NotificationModel()\ - .get_user_notification(c.user.user_id, no) + unotification = NotificationModel() \ + .get_user_notification(self.authuser.user_id, notification) - # if this association to user is not valid, we don't want to show - # this message - if unotification is not None: - if not unotification.read: - unotification.mark_as_read() - Session().commit() - c.notification = no + # if this association to user is not valid, we don't want to show + # this message + if unotification is None: + raise HTTPForbidden() - return render('admin/notifications/show_notification.html') - - return abort(403) + if not unotification.read: + unotification.mark_as_read() + Session().commit() - def edit(self, notification_id, format='html'): - """GET /_admin/notifications/id/edit: Form to edit an existing item""" - # url('edit_notification', notification_id=ID) + c.notification = notification + c.user = self.authuser + return render('admin/notifications/show_notification.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/permissions.py --- a/kallithea/controllers/admin/permissions.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/permissions.py Sat Dec 24 00:34:38 2016 +0100 @@ -31,12 +31,13 @@ import formencode from formencode import htmlfill -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _ +from webob.exc import HTTPFound +from kallithea.config.routing import url from kallithea.lib import helpers as h -from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator +from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator from kallithea.lib.base import BaseController, render from kallithea.model.forms import DefaultPermissionsForm from kallithea.model.permission import PermissionModel @@ -53,7 +54,7 @@ # map.resource('permission', 'permissions') @LoginRequired() - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def __before__(self): super(PermissionsController, self).__before__() @@ -139,7 +140,7 @@ h.flash(_('Error occurred during update of permissions'), category='error') - return redirect(url('admin_permissions')) + raise HTTPFound(location=url('admin_permissions')) c.user = User.get_default_user() defaults = {'anonymous': c.user.active} @@ -184,7 +185,7 @@ def permission_ips(self): c.active = 'ips' c.user = User.get_default_user() - c.user_ip_map = UserIpMap.query()\ + c.user_ip_map = UserIpMap.query() \ .filter(UserIpMap.user == c.user).all() return render('admin/permissions/permissions.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/repo_groups.py --- a/kallithea/controllers/admin/repo_groups.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/repo_groups.py Sat Dec 24 00:34:38 2016 +0100 @@ -32,16 +32,17 @@ from formencode import htmlfill -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import abort, redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _, ungettext +from webob.exc import HTTPFound, HTTPForbidden, HTTPNotFound, HTTPInternalServerError import kallithea +from kallithea.config.routing import url from kallithea.lib import helpers as h from kallithea.lib.compat import json from kallithea.lib.auth import LoginRequired, \ - HasRepoGroupPermissionAnyDecorator, HasRepoGroupPermissionAll, \ - HasPermissionAll + HasRepoGroupPermissionAnyDecorator, HasRepoGroupPermissionAny, \ + HasPermissionAny from kallithea.lib.base import BaseController, render from kallithea.model.db import RepoGroup, Repository from kallithea.model.scm import RepoGroupList, AvailableRepoGroupChoices @@ -49,7 +50,6 @@ from kallithea.model.forms import RepoGroupForm, RepoGroupPermsForm from kallithea.model.meta import Session from kallithea.model.repo import RepoModel -from webob.exc import HTTPInternalServerError, HTTPNotFound from kallithea.lib.utils2 import safe_int from sqlalchemy.sql.expression import func @@ -109,11 +109,7 @@ return False def index(self, format='html'): - """GET /repo_groups: All items in the collection""" - # url('repos_groups') - _list = RepoGroup.query()\ - .order_by(func.lower(RepoGroup.group_name))\ - .all() + _list = RepoGroup.query(sorted=True).all() group_iter = RepoGroupList(_list, perm_set=['group.admin']) repo_groups_data = [] total_records = len(group_iter) @@ -140,7 +136,7 @@ "group_name": repo_group_name(repo_gr.group_name, children_groups), "desc": h.escape(repo_gr.group_description), "repos": repo_count, - "owner": h.person(repo_gr.user), + "owner": h.person(repo_gr.owner), "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name, repo_count) }) @@ -156,9 +152,6 @@ return render('admin/repo_groups/repo_groups.html') def create(self): - """POST /repo_groups: Create a new item""" - # url('repos_groups') - self.__load_defaults() # permissions for can create group based on parent_id are checked @@ -169,7 +162,7 @@ gr = RepoGroupModel().create( group_name=form_result['group_name'], group_description=form_result['group_description'], - parent=form_result['group_parent_id'], + parent=form_result['parent_group_id'], owner=self.authuser.user_id, # TODO: make editable copy_permissions=form_result['group_copy_permissions'] ) @@ -187,17 +180,15 @@ log.error(traceback.format_exc()) h.flash(_('Error occurred during creation of repository group %s') \ % request.POST.get('group_name'), category='error') - parent_group_id = form_result['group_parent_id'] + parent_group_id = form_result['parent_group_id'] #TODO: maybe we should get back to the main view, not the admin one - return redirect(url('repos_groups', parent_group=parent_group_id)) + raise HTTPFound(location=url('repos_groups', parent_group=parent_group_id)) h.flash(_('Created repository group %s') % gr.group_name, category='success') - return redirect(url('repos_group_home', group_name=gr.group_name)) + raise HTTPFound(location=url('repos_group_home', group_name=gr.group_name)) def new(self): - """GET /repo_groups/new: Form to create a new item""" - # url('new_repos_group') - if HasPermissionAll('hg.admin')('group create'): + if HasPermissionAny('hg.admin')('group create'): #we're global admin, we're ok and we can create TOP level groups pass else: @@ -206,30 +197,22 @@ group_id = safe_int(request.GET.get('parent_group')) group = RepoGroup.get(group_id) if group_id else None group_name = group.group_name if group else None - if HasRepoGroupPermissionAll('group.admin')(group_name, 'group create'): + if HasRepoGroupPermissionAny('group.admin')(group_name, 'group create'): pass else: - return abort(403) + raise HTTPForbidden() self.__load_defaults() return render('admin/repo_groups/repo_group_add.html') @HasRepoGroupPermissionAnyDecorator('group.admin') def update(self, group_name): - """PUT /repo_groups/group_name: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('repos_group', group_name=GROUP_NAME), - # method='put') - # url('repos_group', group_name=GROUP_NAME) - c.repo_group = RepoGroupModel()._get_repo_group(group_name) self.__load_defaults(extras=[c.repo_group.parent_group], exclude=[c.repo_group]) # TODO: kill allow_empty_group - it is only used for redundant form validation! - if HasPermissionAll('hg.admin')('group edit'): + if HasPermissionAny('hg.admin')('group edit'): #we're global admin, we're ok and we can create TOP level groups allow_empty_group = True elif not c.repo_group.parent_group: @@ -253,7 +236,7 @@ group_name = new_gr.group_name #TODO: in future action_logger(, '', '', '', self.sa) except formencode.Invalid as errors: - + c.active = 'settings' return htmlfill.render( render('admin/repo_groups/repo_group_edit.html'), defaults=errors.value, @@ -266,30 +249,22 @@ h.flash(_('Error occurred during update of repository group %s') \ % request.POST.get('group_name'), category='error') - return redirect(url('edit_repo_group', group_name=group_name)) + raise HTTPFound(location=url('edit_repo_group', group_name=group_name)) @HasRepoGroupPermissionAnyDecorator('group.admin') def delete(self, group_name): - """DELETE /repo_groups/group_name: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('repos_group', group_name=GROUP_NAME), - # method='delete') - # url('repos_group', group_name=GROUP_NAME) - gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name) repos = gr.repositories.all() if repos: h.flash(_('This group contains %s repositories and cannot be ' 'deleted') % len(repos), category='warning') - return redirect(url('repos_groups')) + raise HTTPFound(location=url('repos_groups')) children = gr.children.all() if children: h.flash(_('This group contains %s subgroups and cannot be deleted' % (len(children))), category='warning') - return redirect(url('repos_groups')) + raise HTTPFound(location=url('repos_groups')) try: RepoGroupModel().delete(group_name) @@ -303,8 +278,8 @@ % group_name, category='error') if gr.parent_group: - return redirect(url('repos_group_home', group_name=gr.parent_group.group_name)) - return redirect(url('repos_groups')) + raise HTTPFound(location=url('repos_group_home', group_name=gr.parent_group.group_name)) + raise HTTPFound(location=url('repos_groups')) def show_by_name(self, group_name): """ @@ -320,27 +295,16 @@ @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write', 'group.admin') def show(self, group_name): - """GET /repo_groups/group_name: Show a specific item""" - # url('repos_group', group_name=GROUP_NAME) c.active = 'settings' c.group = c.repo_group = RepoGroupModel()._get_repo_group(group_name) - c.group_repos = c.group.repositories.all() - #overwrite our cached list with current filter - c.repo_cnt = 0 - - groups = RepoGroup.query().order_by(RepoGroup.group_name)\ - .filter(RepoGroup.group_parent_id == c.group.group_id).all() + groups = RepoGroup.query(sorted=True).filter_by(parent_group=c.group).all() c.groups = self.scm_model.get_repo_groups(groups) - c.repos_list = Repository.query()\ - .filter(Repository.group_id == c.group.group_id)\ - .order_by(func.lower(Repository.repo_name))\ - .all() - - repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list, - admin=False) + repos_list = Repository.query(sorted=True).filter_by(group=c.group).all() + repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, + admin=False, short_name=True) #json used to render the grid c.data = json.dumps(repos_data) @@ -348,8 +312,6 @@ @HasRepoGroupPermissionAnyDecorator('group.admin') def edit(self, group_name): - """GET /repo_groups/group_name/edit: Form to edit an existing item""" - # url('edit_repo_group', group_name=GROUP_NAME) c.active = 'settings' c.repo_group = RepoGroupModel()._get_repo_group(group_name) @@ -366,8 +328,6 @@ @HasRepoGroupPermissionAnyDecorator('group.admin') def edit_repo_group_advanced(self, group_name): - """GET /repo_groups/group_name/edit: Form to edit an existing item""" - # url('edit_repo_group', group_name=GROUP_NAME) c.active = 'advanced' c.repo_group = RepoGroupModel()._get_repo_group(group_name) @@ -375,8 +335,6 @@ @HasRepoGroupPermissionAnyDecorator('group.admin') def edit_repo_group_perms(self, group_name): - """GET /repo_groups/group_name/edit: Form to edit an existing item""" - # url('edit_repo_group', group_name=GROUP_NAME) c.active = 'perms' c.repo_group = RepoGroupModel()._get_repo_group(group_name) self.__load_defaults() @@ -404,7 +362,7 @@ if self._revoke_perms_on_yourself(form_result): msg = _('Cannot revoke permission for yourself as admin') h.flash(msg, category='warning') - return redirect(url('edit_repo_group_perms', group_name=group_name)) + raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name)) recursive = form_result['recursive'] # iterate over all members(if in recursive mode) of this groups and # set the permissions ! @@ -418,15 +376,10 @@ # repo_name, self.ip_addr, self.sa) Session().commit() h.flash(_('Repository group permissions updated'), category='success') - return redirect(url('edit_repo_group_perms', group_name=group_name)) + raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name)) @HasRepoGroupPermissionAnyDecorator('group.admin') def delete_perms(self, group_name): - """ - DELETE an existing repository group permission user - - :param group_name: - """ try: obj_type = request.POST.get('obj_type') obj_id = None diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/repos.py --- a/kallithea/controllers/admin/repos.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/repos.py Sat Dec 24 00:34:38 2016 +0100 @@ -29,21 +29,20 @@ import traceback import formencode from formencode import htmlfill -from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _ from sqlalchemy.sql.expression import func +from webob.exc import HTTPFound, HTTPInternalServerError, HTTPForbidden, HTTPNotFound +from kallithea.config.routing import url from kallithea.lib import helpers as h from kallithea.lib.auth import LoginRequired, \ - HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny, \ - HasRepoPermissionAnyDecorator + HasRepoPermissionAnyDecorator, NotAnonymous, HasPermissionAny from kallithea.lib.base import BaseRepoController, render from kallithea.lib.utils import action_logger, jsonify from kallithea.lib.vcs import RepositoryError from kallithea.model.meta import Session -from kallithea.model.db import User, Repository, UserFollowing, RepoGroup,\ +from kallithea.model.db import User, Repository, UserFollowing, RepoGroup, \ Setting, RepositoryField from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices, RepoList @@ -66,12 +65,12 @@ def __before__(self): super(ReposController, self).__before__() - def _load_repo(self, repo_name): - repo_obj = Repository.get_by_repo_name(repo_name) + def _load_repo(self): + repo_obj = c.db_repo if repo_obj is None: - h.not_mapped_error(repo_name) - return redirect(url('repos')) + h.not_mapped_error(c.repo_name) + raise HTTPFound(location=url('repos')) return repo_obj @@ -86,26 +85,20 @@ c.landing_revs_choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo) - def __load_data(self, repo_name=None): + def __load_data(self): """ Load defaults settings for edit, and update - - :param repo_name: """ - c.repo_info = self._load_repo(repo_name) + c.repo_info = self._load_repo() self.__load_defaults(c.repo_info) - defaults = RepoModel()._get_defaults(repo_name) + defaults = RepoModel()._get_defaults(c.repo_name) defaults['clone_uri'] = c.repo_info.clone_uri_hidden # don't show password return defaults def index(self, format='html'): - """GET /repos: All items in the collection""" - # url('repos') - _list = Repository.query()\ - .order_by(func.lower(Repository.repo_name))\ - .all() + _list = Repository.query(sorted=True).all() c.repos_list = RepoList(_list, perm_set=['repository.admin']) repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list, @@ -118,25 +111,18 @@ @NotAnonymous() def create(self): - """ - POST /repos: Create a new item""" - # url('repos') - self.__load_defaults() form_result = {} - task_id = None try: # CanWriteGroup validators checks permissions of this POST form_result = RepoForm(repo_groups=c.repo_groups, - landing_revs=c.landing_revs_choices)()\ + landing_revs=c.landing_revs_choices)() \ .to_python(dict(request.POST)) # create is done sometimes async on celery, db transaction # management is handled there. task = RepoModel().create(form_result, self.authuser.user_id) - from celery.result import BaseAsyncResult - if isinstance(task, BaseAsyncResult): - task_id = task.task_id + task_id = task.task_id except formencode.Invalid as errors: log.info(errors) return htmlfill.render( @@ -152,15 +138,14 @@ msg = (_('Error creating repository %s') % form_result.get('repo_name')) h.flash(msg, category='error') - return redirect(url('home')) + raise HTTPFound(location=url('home')) - return redirect(h.url('repo_creating_home', + raise HTTPFound(location=h.url('repo_creating_home', repo_name=form_result['repo_name_full'], task_id=task_id)) @NotAnonymous() def create_repository(self): - """GET /_admin/create_repository: Form to create a new item""" self.__load_defaults() if not c.repo_groups: raise HTTPForbidden @@ -201,9 +186,9 @@ if task_id and task_id not in ['None']: from kallithea import CELERY_ON - from celery.result import AsyncResult + from kallithea.lib import celerypylons if CELERY_ON: - task = AsyncResult(task_id) + task = celerypylons.result.AsyncResult(task_id) if task.failed(): raise HTTPInternalServerError(task.traceback) @@ -227,20 +212,12 @@ return {'result': True} return {'result': False} - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def update(self, repo_name): - """ - PUT /repos/repo_name: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('put_repo', repo_name=ID), - # method='put') - # url('put_repo', repo_name=ID) - c.repo_info = self._load_repo(repo_name) + c.repo_info = self._load_repo() self.__load_defaults(c.repo_info) c.active = 'settings' - c.repo_fields = RepositoryField.query()\ + c.repo_fields = RepositoryField.query() \ .filter(RepositoryField.repository == c.repo_info).all() repo_model = RepoModel() @@ -267,7 +244,7 @@ Session().commit() except formencode.Invalid as errors: log.info(errors) - defaults = self.__load_data(repo_name) + defaults = self.__load_data() defaults.update(errors.value) c.users_array = repo_model.get_users_js() return htmlfill.render( @@ -282,24 +259,15 @@ log.error(traceback.format_exc()) h.flash(_('Error occurred during update of repository %s') \ % repo_name, category='error') - return redirect(url('edit_repo', repo_name=changed_name)) + raise HTTPFound(location=url('edit_repo', repo_name=changed_name)) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def delete(self, repo_name): - """ - DELETE /repos/repo_name: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('delete_repo', repo_name=ID), - # method='delete') - # url('delete_repo', repo_name=ID) - repo_model = RepoModel() repo = repo_model.get_by_repo_name(repo_name) if not repo: h.not_mapped_error(repo_name) - return redirect(url('repos')) + raise HTTPFound(location=url('repos')) try: _forks = repo.forks.count() handle_forks = None @@ -327,15 +295,13 @@ category='error') if repo.group: - return redirect(url('repos_group_home', group_name=repo.group.group_name)) - return redirect(url('repos')) + raise HTTPFound(location=url('repos_group_home', group_name=repo.group.group_name)) + raise HTTPFound(location=url('repos')) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - # url('edit_repo', repo_name=ID) - defaults = self.__load_data(repo_name) - c.repo_fields = RepositoryField.query()\ + defaults = self.__load_data() + c.repo_fields = RepositoryField.query() \ .filter(RepositoryField.repository == c.repo_info).all() repo_model = RepoModel() c.users_array = repo_model.get_users_js() @@ -346,11 +312,9 @@ encoding="UTF-8", force_defaults=False) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_permissions(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - # url('edit_repo', repo_name=ID) - c.repo_info = self._load_repo(repo_name) + c.repo_info = self._load_repo() repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.user_groups_array = repo_model.get_user_groups_js() @@ -372,7 +336,7 @@ # repo_name, self.ip_addr, self.sa) Session().commit() h.flash(_('Repository permissions updated'), category='success') - return redirect(url('edit_repo_perms', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_perms', repo_name=repo_name)) def edit_permissions_revoke(self, repo_name): try: @@ -399,20 +363,18 @@ category='error') raise HTTPInternalServerError() - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_fields(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - # url('edit_repo', repo_name=ID) - c.repo_info = self._load_repo(repo_name) - c.repo_fields = RepositoryField.query()\ + c.repo_info = self._load_repo() + c.repo_fields = RepositoryField.query() \ .filter(RepositoryField.repository == c.repo_info).all() c.active = 'fields' if request.POST: - return redirect(url('repo_edit_fields')) + raise HTTPFound(location=url('repo_edit_fields')) return render('admin/repos/repo_edit.html') - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def create_repo_field(self, repo_name): try: form_result = RepoFieldForm()().to_python(dict(request.POST)) @@ -431,9 +393,9 @@ if isinstance(e, formencode.Invalid): msg += ". " + e.msg h.flash(msg, category='error') - return redirect(url('edit_repo_fields', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name)) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def delete_repo_field(self, repo_name, field_id): field = RepositoryField.get_or_404(field_id) try: @@ -443,19 +405,17 @@ log.error(traceback.format_exc()) msg = _('An error occurred during removal of field') h.flash(msg, category='error') - return redirect(url('edit_repo_fields', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name)) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_advanced(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - # url('edit_repo', repo_name=ID) - c.repo_info = self._load_repo(repo_name) + c.repo_info = self._load_repo() c.default_user_id = User.get_default_user().user_id - c.in_public_journal = UserFollowing.query()\ - .filter(UserFollowing.user_id == c.default_user_id)\ + c.in_public_journal = UserFollowing.query() \ + .filter(UserFollowing.user_id == c.default_user_id) \ .filter(UserFollowing.follows_repository == c.repo_info).scalar() - _repos = Repository.query().order_by(Repository.repo_name).all() + _repos = Repository.query(sorted=True).all() read_access_repos = RepoList(_repos) c.repos_list = [(None, _('-- Not a fork --'))] c.repos_list += [(x.repo_id, x.repo_name) @@ -463,19 +423,19 @@ if x.repo_id != c.repo_info.repo_id] defaults = { - 'id_fork_of': c.repo_info.fork.repo_id if c.repo_info.fork else '' + 'id_fork_of': c.repo_info.fork_id if c.repo_info.fork_id else '' } c.active = 'advanced' if request.POST: - return redirect(url('repo_edit_advanced')) + raise HTTPFound(location=url('repo_edit_advanced')) return htmlfill.render( render('admin/repos/repo_edit.html'), defaults=defaults, encoding="UTF-8", force_defaults=False) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_advanced_journal(self, repo_name): """ Sets this repository to be visible in public journal, @@ -495,10 +455,10 @@ h.flash(_('An error occurred during setting this' ' repository in public journal'), category='error') - return redirect(url('edit_repo_advanced', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name)) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_advanced_fork(self, repo_name): """ Mark given repository as a fork of another @@ -521,9 +481,9 @@ h.flash(_('An error occurred during this operation'), category='error') - return redirect(url('edit_repo_advanced', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name)) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_advanced_locking(self, repo_name): """ Unlock repository when it is locked ! @@ -542,16 +502,10 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during unlocking'), category='error') - return redirect(url('edit_repo_advanced', repo_name=repo_name)) + raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name)) @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') def toggle_locking(self, repo_name): - """ - Toggle locking of repository by simple GET call to url - - :param repo_name: - """ - try: repo = Repository.get_by_repo_name(repo_name) @@ -567,17 +521,15 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during unlocking'), category='error') - return redirect(url('summary_home', repo_name=repo_name)) + raise HTTPFound(location=url('summary_home', repo_name=repo_name)) - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_caches(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - # url('edit_repo', repo_name=ID) - c.repo_info = self._load_repo(repo_name) + c.repo_info = self._load_repo() c.active = 'caches' if request.POST: try: - ScmModel().mark_for_invalidation(repo_name, delete=True) + ScmModel().mark_for_invalidation(repo_name) Session().commit() h.flash(_('Cache invalidation successful'), category='success') @@ -586,14 +538,12 @@ h.flash(_('An error occurred during cache invalidation'), category='error') - return redirect(url('edit_repo_caches', repo_name=c.repo_name)) + raise HTTPFound(location=url('edit_repo_caches', repo_name=c.repo_name)) return render('admin/repos/repo_edit.html') - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_remote(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - # url('edit_repo', repo_name=ID) - c.repo_info = self._load_repo(repo_name) + c.repo_info = self._load_repo() c.active = 'remote' if request.POST: try: @@ -603,14 +553,12 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during pull from remote location'), category='error') - return redirect(url('edit_repo_remote', repo_name=c.repo_name)) + raise HTTPFound(location=url('edit_repo_remote', repo_name=c.repo_name)) return render('admin/repos/repo_edit.html') - @HasRepoPermissionAllDecorator('repository.admin') + @HasRepoPermissionAnyDecorator('repository.admin') def edit_statistics(self, repo_name): - """GET /repo_name/settings: Form to edit an existing item""" - # url('edit_repo', repo_name=ID) - c.repo_info = self._load_repo(repo_name) + c.repo_info = self._load_repo() repo = c.repo_info.scm_instance if c.repo_info.stats: @@ -636,6 +584,6 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of repository stats'), category='error') - return redirect(url('edit_repo_statistics', repo_name=c.repo_name)) + raise HTTPFound(location=url('edit_repo_statistics', repo_name=c.repo_name)) return render('admin/repos/repo_edit.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/settings.py --- a/kallithea/controllers/admin/settings.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/settings.py Sat Dec 24 00:34:38 2016 +0100 @@ -30,14 +30,15 @@ import formencode from formencode import htmlfill -from pylons import request, tmpl_context as c, url, config -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c, config from pylons.i18n.translation import _ +from webob.exc import HTTPFound +from kallithea.config.routing import url from kallithea.lib import helpers as h -from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator +from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator from kallithea.lib.base import BaseController, render -from kallithea.lib.celerylib import tasks, run_task +from kallithea.lib.celerylib import tasks from kallithea.lib.exceptions import HgsubversionImportError from kallithea.lib.utils import repo2db_mapper, set_app_settings from kallithea.model.db import Ui, Repository, Setting @@ -64,31 +65,23 @@ def _get_hg_ui_settings(self): ret = Ui.query().all() - if not ret: - raise Exception('Could not get application ui settings !') settings = {} for each in ret: - k = each.ui_key + k = each.ui_section + '_' + each.ui_key v = each.ui_value - if k == '/': - k = 'root_path' + if k == 'paths_/': + k = 'paths_root_path' - if k == 'push_ssl': - v = str2bool(v) - - if k.find('.') != -1: - k = k.replace('.', '_') + k = k.replace('.', '_') if each.ui_section in ['hooks', 'extensions']: v = each.ui_active - settings[each.ui_section + '_' + k] = v + settings[k] = v return settings - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_vcs(self): - """GET /admin/settings: All items in the collection""" - # url('admin_settings') c.active = 'vcs' if request.POST: application_form = ApplicationUiSettingsForm()() @@ -104,66 +97,37 @@ force_defaults=False) try: - sett = Ui.get_by_key('push_ssl') - sett.ui_value = form_result['web_push_ssl'] - Session().add(sett) if c.visual.allow_repo_location_change: - sett = Ui.get_by_key('/') + sett = Ui.get_by_key('paths', '/') sett.ui_value = form_result['paths_root_path'] - Session().add(sett) #HOOKS - sett = Ui.get_by_key(Ui.HOOK_UPDATE) + sett = Ui.get_by_key('hooks', Ui.HOOK_UPDATE) sett.ui_active = form_result['hooks_changegroup_update'] - Session().add(sett) - sett = Ui.get_by_key(Ui.HOOK_REPO_SIZE) + sett = Ui.get_by_key('hooks', Ui.HOOK_REPO_SIZE) sett.ui_active = form_result['hooks_changegroup_repo_size'] - Session().add(sett) - sett = Ui.get_by_key(Ui.HOOK_PUSH) + sett = Ui.get_by_key('hooks', Ui.HOOK_PUSH) sett.ui_active = form_result['hooks_changegroup_push_logger'] - Session().add(sett) - sett = Ui.get_by_key(Ui.HOOK_PULL) + sett = Ui.get_by_key('hooks', Ui.HOOK_PULL) sett.ui_active = form_result['hooks_outgoing_pull_logger'] - Session().add(sett) - ## EXTENSIONS - sett = Ui.get_by_key('largefiles') - if not sett: - #make one if it's not there ! - sett = Ui() - sett.ui_key = 'largefiles' - sett.ui_section = 'extensions' + sett = Ui.get_or_create('extensions', 'largefiles') sett.ui_active = form_result['extensions_largefiles'] - Session().add(sett) - sett = Ui.get_by_key('hgsubversion') - if not sett: - #make one if it's not there ! - sett = Ui() - sett.ui_key = 'hgsubversion' - sett.ui_section = 'extensions' - + sett = Ui.get_or_create('extensions', 'hgsubversion') sett.ui_active = form_result['extensions_hgsubversion'] if sett.ui_active: try: import hgsubversion # pragma: no cover except ImportError: raise HgsubversionImportError - Session().add(sett) -# sett = Ui.get_by_key('hggit') -# if not sett: -# #make one if it's not there ! -# sett = Ui() -# sett.ui_key = 'hggit' -# sett.ui_section = 'extensions' -# +# sett = Ui.get_or_create('extensions', 'hggit') # sett.ui_active = form_result['extensions_hggit'] -# Session().add(sett) Session().commit() @@ -189,10 +153,8 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_mapping(self): - """GET /admin/settings/mapping: All items in the collection""" - # url('admin_settings_mapping') c.active = 'mapping' if request.POST: rm_obsolete = request.POST.get('destroy', False) @@ -205,8 +167,8 @@ if invalidate_cache: log.debug('invalidating all repositories cache') - for repo in Repository.get_all(): - ScmModel().mark_for_invalidation(repo.repo_name, delete=True) + for repo in Repository.query(): + ScmModel().mark_for_invalidation(repo.repo_name) filesystem_repos = ScmModel().repo_scan() added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, @@ -218,7 +180,7 @@ for repo_name in added) or '-', ', '.join(h.escape(safe_unicode(repo_name)) for repo_name in removed) or '-')), category='success') - return redirect(url('admin_settings_mapping')) + raise HTTPFound(location=url('admin_settings_mapping')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -229,10 +191,8 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_global(self): - """GET /admin/settings/global: All items in the collection""" - # url('admin_settings_global') c.active = 'global' if request.POST: application_form = ApplicationSettingsForm()() @@ -248,25 +208,14 @@ force_defaults=False) try: - sett1 = Setting.create_or_update('title', - form_result['title']) - Session().add(sett1) - - sett2 = Setting.create_or_update('realm', - form_result['realm']) - Session().add(sett2) - - sett3 = Setting.create_or_update('ga_code', - form_result['ga_code']) - Session().add(sett3) - - sett4 = Setting.create_or_update('captcha_public_key', - form_result['captcha_public_key']) - Session().add(sett4) - - sett5 = Setting.create_or_update('captcha_private_key', - form_result['captcha_private_key']) - Session().add(sett5) + for setting in ( + 'title', + 'realm', + 'ga_code', + 'captcha_public_key', + 'captcha_private_key', + ): + Setting.create_or_update(setting, form_result[setting]) Session().commit() set_app_settings(config) @@ -278,7 +227,7 @@ 'application settings'), category='error') - return redirect(url('admin_settings_global')) + raise HTTPFound(location=url('admin_settings_global')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -289,10 +238,8 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_visual(self): - """GET /admin/settings/visual: All items in the collection""" - # url('admin_settings_visual') c.active = 'visual' if request.POST: application_form = ApplicationVisualisationForm()() @@ -321,9 +268,7 @@ ('clone_uri_tmpl', 'clone_uri_tmpl', 'unicode'), ] for setting, form_key, type_ in settings: - sett = Setting.create_or_update(setting, - form_result[form_key], type_) - Session().add(sett) + Setting.create_or_update(setting, form_result[form_key], type_) Session().commit() set_app_settings(config) @@ -336,7 +281,7 @@ 'visualisation settings'), category='error') - return redirect(url('admin_settings_visual')) + raise HTTPFound(location=url('admin_settings_visual')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -347,10 +292,8 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_email(self): - """GET /admin/settings/email: All items in the collection""" - # url('admin_settings_email') c.active = 'email' if request.POST: test_email = request.POST.get('test_email') @@ -359,22 +302,22 @@ 'Kallithea version: %s' % c.kallithea_version) if not test_email: h.flash(_('Please enter email address'), category='error') - return redirect(url('admin_settings_email')) + raise HTTPFound(location=url('admin_settings_email')) - test_email_txt_body = EmailNotificationModel()\ + test_email_txt_body = EmailNotificationModel() \ .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT, 'txt', body=test_body) - test_email_html_body = EmailNotificationModel()\ + test_email_html_body = EmailNotificationModel() \ .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT, 'html', body=test_body) recipients = [test_email] if test_email else None - run_task(tasks.send_email, recipients, test_email_subj, - test_email_txt_body, test_email_html_body) + tasks.send_email(recipients, test_email_subj, + test_email_txt_body, test_email_html_body) h.flash(_('Send email task created'), category='success') - return redirect(url('admin_settings_email')) + raise HTTPFound(location=url('admin_settings_email')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -388,10 +331,8 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_hooks(self): - """GET /admin/settings/hooks: All items in the collection""" - # url('admin_settings_hooks') c.active = 'hooks' if request.POST: if c.visual.allow_custom_hooks_settings: @@ -425,7 +366,7 @@ h.flash(_('Error occurred during hook creation'), category='error') - return redirect(url('admin_settings_hooks')) + raise HTTPFound(location=url('admin_settings_hooks')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -439,17 +380,15 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_search(self): - """GET /admin/settings/search: All items in the collection""" - # url('admin_settings_search') c.active = 'search' if request.POST: 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) + tasks.whoosh_index(repo_location, full_index) h.flash(_('Whoosh reindex task scheduled'), category='success') - return redirect(url('admin_settings_search')) + raise HTTPFound(location=url('admin_settings_search')) defaults = Setting.get_app_settings() defaults.update(self._get_hg_ui_settings()) @@ -460,10 +399,8 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_system(self): - """GET /admin/settings/system: All items in the collection""" - # url('admin_settings_system') c.active = 'system' defaults = Setting.get_app_settings() @@ -482,10 +419,8 @@ encoding="UTF-8", force_defaults=False) - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def settings_system_update(self): - """GET /admin/settings/system/updates: All items in the collection""" - # url('admin_settings_system_update') import json import urllib2 from kallithea.lib.verlib import NormalizedVersion @@ -496,7 +431,7 @@ _update_url = defaults.get('update_url', '') _update_url = "" # FIXME: disabled - _err = lambda s: '
%s
' % (s) + _err = lambda s: '
%s
' % (s) try: import kallithea ver = kallithea.__version__ diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/user_groups.py --- a/kallithea/controllers/admin/user_groups.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/user_groups.py Sat Dec 24 00:34:38 2016 +0100 @@ -15,7 +15,7 @@ kallithea.controllers.admin.user_groups ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -User Groups crud controller for pylons +User Groups crud controller This file was forked by the Kallithea project in July 2014. Original author and date, and relevant copyright and licensing information is below: @@ -30,15 +30,16 @@ import formencode from formencode import htmlfill -from pylons import request, tmpl_context as c, url, config -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c, config from pylons.i18n.translation import _ +from webob.exc import HTTPFound from sqlalchemy.orm import joinedload from sqlalchemy.sql.expression import func from webob.exc import HTTPInternalServerError import kallithea +from kallithea.config.routing import url from kallithea.lib import helpers as h from kallithea.lib.exceptions import UserGroupsAssignedException, \ RepoGroupAssignmentError @@ -88,10 +89,8 @@ return data def index(self, format='html'): - """GET /users_groups: All items in the collection""" - # url('users_groups') - _list = UserGroup.query()\ - .order_by(func.lower(UserGroup.users_group_name))\ + _list = UserGroup.query() \ + .order_by(func.lower(UserGroup.users_group_name)) \ .all() group_iter = UserGroupList(_list, perm_set=['usergroup.admin']) user_groups_data = [] @@ -116,7 +115,7 @@ "desc": h.escape(user_gr.user_group_description), "members": len(user_gr.members), "active": h.boolicon(user_gr.users_group_active), - "owner": h.person(user_gr.user.username), + "owner": h.person(user_gr.owner.username), "action": user_group_actions(user_gr.users_group_id, user_gr.users_group_name) }) @@ -132,9 +131,6 @@ @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') def create(self): - """POST /users_groups: Create a new item""" - # url('users_groups') - users_group_form = UserGroupForm()() try: form_result = users_group_form.to_python(dict(request.POST)) @@ -163,24 +159,14 @@ h.flash(_('Error occurred during creation of user group %s') \ % request.POST.get('users_group_name'), category='error') - return redirect(url('users_groups')) + raise HTTPFound(location=url('users_groups')) @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') def new(self, format='html'): - """GET /user_groups/new: Form to create a new item""" - # url('new_users_group') return render('admin/user_groups/user_group_add.html') @HasUserGroupPermissionAnyDecorator('usergroup.admin') def update(self, id): - """PUT /user_groups/id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('users_group', id=ID), - # method='put') - # url('users_group', id=ID) - c.user_group = UserGroup.get_or_404(id) c.active = 'settings' self.__load_data(id) @@ -209,7 +195,6 @@ 'hg.create.repository'), 'fork_repo_perm': ug_model.has_perm(id, 'hg.fork.repository'), - '_method': 'put' }) return htmlfill.render( @@ -224,17 +209,10 @@ h.flash(_('Error occurred during update of user group %s') \ % request.POST.get('users_group_name'), category='error') - return redirect(url('edit_users_group', id=id)) + raise HTTPFound(location=url('edit_users_group', id=id)) @HasUserGroupPermissionAnyDecorator('usergroup.admin') def delete(self, id): - """DELETE /user_groups/id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('users_group', id=ID), - # method='delete') - # url('users_group', id=ID) usr_gr = UserGroup.get_or_404(id) try: UserGroupModel().delete(usr_gr) @@ -246,17 +224,10 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of user group'), category='error') - return redirect(url('users_groups')) - - def show(self, id, format='html'): - """GET /user_groups/id: Show a specific item""" - # url('users_group', id=ID) + raise HTTPFound(location=url('users_groups')) @HasUserGroupPermissionAnyDecorator('usergroup.admin') def edit(self, id, format='html'): - """GET /user_groups/id/edit: Form to edit an existing item""" - # url('edit_users_group', id=ID) - c.user_group = UserGroup.get_or_404(id) c.active = 'settings' self.__load_data(id) @@ -312,21 +283,16 @@ form['perms_updates']) except RepoGroupAssignmentError: h.flash(_('Target group cannot be the same'), category='error') - return redirect(url('edit_user_group_perms', id=id)) + raise HTTPFound(location=url('edit_user_group_perms', id=id)) #TODO: implement this #action_logger(self.authuser, 'admin_changed_repo_permissions', # repo_name, self.ip_addr, self.sa) Session().commit() h.flash(_('User group permissions updated'), category='success') - return redirect(url('edit_user_group_perms', id=id)) + raise HTTPFound(location=url('edit_user_group_perms', id=id)) @HasUserGroupPermissionAnyDecorator('usergroup.admin') def delete_perms(self, id): - """ - DELETE an existing repository group permission user - - :param group_name: - """ try: obj_type = request.POST.get('obj_type') obj_id = None @@ -362,20 +328,20 @@ 'repositories': {}, 'repositories_groups': {} } - ugroup_repo_perms = UserGroupRepoToPerm.query()\ - .options(joinedload(UserGroupRepoToPerm.permission))\ - .options(joinedload(UserGroupRepoToPerm.repository))\ - .filter(UserGroupRepoToPerm.users_group_id == id)\ + ugroup_repo_perms = UserGroupRepoToPerm.query() \ + .options(joinedload(UserGroupRepoToPerm.permission)) \ + .options(joinedload(UserGroupRepoToPerm.repository)) \ + .filter(UserGroupRepoToPerm.users_group_id == id) \ .all() for gr in ugroup_repo_perms: permissions['repositories'][gr.repository.repo_name] \ = gr.permission.permission_name - ugroup_group_perms = UserGroupRepoGroupToPerm.query()\ - .options(joinedload(UserGroupRepoGroupToPerm.permission))\ - .options(joinedload(UserGroupRepoGroupToPerm.group))\ - .filter(UserGroupRepoGroupToPerm.users_group_id == id)\ + ugroup_group_perms = UserGroupRepoGroupToPerm.query() \ + .options(joinedload(UserGroupRepoGroupToPerm.permission)) \ + .options(joinedload(UserGroupRepoGroupToPerm.group)) \ + .filter(UserGroupRepoGroupToPerm.users_group_id == id) \ .all() for gr in ugroup_group_perms: @@ -404,9 +370,6 @@ @HasUserGroupPermissionAnyDecorator('usergroup.admin') def update_default_perms(self, id): - """PUT /users_perm/id: Update an existing item""" - # url('users_group_perm', id=ID, method='put') - user_group = UserGroup.get_or_404(id) try: @@ -415,11 +378,10 @@ inherit_perms = form_result['inherit_default_permissions'] user_group.inherit_default_permissions = inherit_perms - Session().add(user_group) usergroup_model = UserGroupModel() - defs = UserGroupToPerm.query()\ - .filter(UserGroupToPerm.users_group == user_group)\ + defs = UserGroupToPerm.query() \ + .filter(UserGroupToPerm.users_group == user_group) \ .all() for ug in defs: Session().delete(ug) @@ -444,7 +406,7 @@ h.flash(_('An error occurred during permissions saving'), category='error') - return redirect(url('edit_user_group_default_perms', id=id)) + raise HTTPFound(location=url('edit_user_group_default_perms', id=id)) @HasUserGroupPermissionAnyDecorator('usergroup.admin') def edit_advanced(self, id): diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/admin/users.py --- a/kallithea/controllers/admin/users.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/admin/users.py Sat Dec 24 00:34:38 2016 +0100 @@ -15,7 +15,7 @@ kallithea.controllers.admin.users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Users crud controller for pylons +Users crud controller This file was forked by the Kallithea project in July 2014. Original author and date, and relevant copyright and licensing information is below: @@ -30,20 +30,19 @@ import formencode from formencode import htmlfill -from pylons import request, tmpl_context as c, url, config -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c, config from pylons.i18n.translation import _ from sqlalchemy.sql.expression import func -from webob.exc import HTTPNotFound +from webob.exc import HTTPFound, HTTPNotFound import kallithea +from kallithea.config.routing import url from kallithea.lib.exceptions import DefaultUserException, \ UserOwnsReposException, UserCreationError from kallithea.lib import helpers as h -from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \ +from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator, \ AuthUser from kallithea.lib import auth_modules -from kallithea.lib.auth_modules import auth_internal from kallithea.lib.base import BaseController, render from kallithea.model.api_key import ApiKeyModel @@ -62,19 +61,15 @@ """REST Controller styled on the Atom Publishing Protocol""" @LoginRequired() - @HasPermissionAllDecorator('hg.admin') + @HasPermissionAnyDecorator('hg.admin') def __before__(self): super(UsersController, self).__before__() c.available_permissions = config['available_permissions'] - c.EXTERN_TYPE_INTERNAL = kallithea.EXTERN_TYPE_INTERNAL def index(self, format='html'): - """GET /users: All items in the collection""" - # url('users') - - c.users_list = User.query().order_by(User.username)\ - .filter(User.username != User.DEFAULT_USER)\ - .order_by(func.lower(User.username))\ + c.users_list = User.query().order_by(User.username) \ + .filter(User.username != User.DEFAULT_USER) \ + .order_by(func.lower(User.username)) \ .all() users_data = [] @@ -119,19 +114,16 @@ return render('admin/users/users.html') def create(self): - """POST /users: Create a new item""" - # url('users') - c.default_extern_type = auth_internal.KallitheaAuthPlugin.name - c.default_extern_name = auth_internal.KallitheaAuthPlugin.name + c.default_extern_type = User.DEFAULT_AUTH_TYPE + c.default_extern_name = '' user_model = UserModel() user_form = UserForm()() try: form_result = user_form.to_python(dict(request.POST)) user = user_model.create(form_result) - usr = form_result['username'] - action_logger(self.authuser, 'admin_created_user:%s' % usr, + action_logger(self.authuser, 'admin_created_user:%s' % user.username, None, self.ip_addr, self.sa) - h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))), + h.flash(_('Created user %s') % user.username, category='success') Session().commit() except formencode.Invalid as errors: @@ -148,23 +140,14 @@ log.error(traceback.format_exc()) h.flash(_('Error occurred during creation of user %s') \ % request.POST.get('username'), category='error') - return redirect(url('users')) + raise HTTPFound(location=url('edit_user', id=user.user_id)) def new(self, format='html'): - """GET /users/new: Form to create a new item""" - # url('new_user') - c.default_extern_type = auth_internal.KallitheaAuthPlugin.name - c.default_extern_name = auth_internal.KallitheaAuthPlugin.name + c.default_extern_type = User.DEFAULT_AUTH_TYPE + c.default_extern_name = '' return render('admin/users/user_add.html') def update(self, id): - """PUT /users/id: Update an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('update_user', id=ID), - # method='put') - # url('user', id=ID) user_model = UserModel() user = user_model.get(id) _form = UserForm(edit=True, old_data={'user_id': id, @@ -188,7 +171,6 @@ '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( self._render_edit_profile(user), @@ -201,16 +183,9 @@ log.error(traceback.format_exc()) h.flash(_('Error occurred during update of user %s') \ % form_result.get('username'), category='error') - return redirect(url('edit_user', id=id)) + raise HTTPFound(location=url('edit_user', id=id)) def delete(self, id): - """DELETE /users/id: Delete an existing item""" - # Forms posted to this method should contain a hidden field: - # - # Or using helpers: - # h.form(url('delete_user', id=ID), - # method='delete') - # url('user', id=ID) usr = User.get_or_404(id) try: UserModel().delete(usr) @@ -222,12 +197,7 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of user'), category='error') - return redirect(url('users')) - - def show(self, id, format='html'): - """GET /users/id: Show a specific item""" - # url('user', id=ID) - User.get_or_404(-1) + raise HTTPFound(location=url('users')) def _get_user_or_raise_if_default(self, id): try: @@ -246,8 +216,6 @@ return render('admin/users/user_edit.html') def edit(self, id, format='html'): - """GET /users/id/edit: Form to edit an existing item""" - # url('edit_user', id=ID) user = self._get_user_or_raise_if_default(id) defaults = user.get_dict() @@ -260,7 +228,7 @@ def edit_advanced(self, id): c.user = self._get_user_or_raise_if_default(id) c.active = 'advanced' - c.perm_user = AuthUser(user_id=id) + c.perm_user = AuthUser(dbuser=c.user) c.ip_addr = self.ip_addr umodel = UserModel() @@ -306,25 +274,22 @@ ApiKeyModel().create(c.user.user_id, description, lifetime) Session().commit() h.flash(_("API key successfully created"), category='success') - return redirect(url('edit_user_api_keys', id=c.user.user_id)) + raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id)) def delete_api_key(self, id): c.user = self._get_user_or_raise_if_default(id) api_key = request.POST.get('del_api_key') if request.POST.get('del_api_key_builtin'): - user = User.get(c.user.user_id) - if user is not None: - user.api_key = generate_api_key() - Session().add(user) - Session().commit() - h.flash(_("API key successfully reset"), category='success') + c.user.api_key = generate_api_key() + Session().commit() + h.flash(_("API key successfully reset"), category='success') elif api_key: ApiKeyModel().delete(api_key, c.user.user_id) Session().commit() h.flash(_("API key successfully deleted"), category='success') - return redirect(url('edit_user_api_keys', id=c.user.user_id)) + raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id)) def update_account(self, id): pass @@ -332,7 +297,7 @@ def edit_perms(self, id): c.user = self._get_user_or_raise_if_default(id) c.active = 'perms' - c.perm_user = AuthUser(user_id=id) + c.perm_user = AuthUser(dbuser=c.user) c.ip_addr = self.ip_addr umodel = UserModel() @@ -350,8 +315,6 @@ force_defaults=False) def update_perms(self, id): - """PUT /users_perm/id: Update an existing item""" - # url('user_perm', id=ID, method='put') user = self._get_user_or_raise_if_default(id) try: @@ -360,11 +323,10 @@ inherit_perms = form_result['inherit_default_permissions'] user.inherit_default_permissions = inherit_perms - Session().add(user) user_model = UserModel() - defs = UserToPerm.query()\ - .filter(UserToPerm.user == user)\ + defs = UserToPerm.query() \ + .filter(UserToPerm.user == user) \ .all() for ug in defs: Session().delete(ug) @@ -387,12 +349,12 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during permissions saving'), category='error') - return redirect(url('edit_user_perms', id=id)) + raise HTTPFound(location=url('edit_user_perms', id=id)) def edit_emails(self, id): c.user = self._get_user_or_raise_if_default(id) c.active = 'emails' - c.user_email_map = UserEmailMap.query()\ + c.user_email_map = UserEmailMap.query() \ .filter(UserEmailMap.user == c.user).all() defaults = c.user.get_dict() @@ -403,8 +365,6 @@ force_defaults=False) def add_email(self, id): - """POST /user_emails:Add an existing item""" - # url('user_emails', id=ID, method='put') user = self._get_user_or_raise_if_default(id) email = request.POST.get('new_email') user_model = UserModel() @@ -420,27 +380,25 @@ log.error(traceback.format_exc()) h.flash(_('An error occurred during email saving'), category='error') - return redirect(url('edit_user_emails', id=id)) + raise HTTPFound(location=url('edit_user_emails', id=id)) def delete_email(self, id): - """DELETE /user_emails_delete/id: Delete an existing item""" - # url('user_emails_delete', id=ID, method='delete') user = self._get_user_or_raise_if_default(id) email_id = request.POST.get('del_email_id') user_model = UserModel() user_model.delete_extra_email(id, email_id) Session().commit() h.flash(_("Removed email from user"), category='success') - return redirect(url('edit_user_emails', id=id)) + raise HTTPFound(location=url('edit_user_emails', id=id)) def edit_ips(self, id): c.user = self._get_user_or_raise_if_default(id) c.active = 'ips' - c.user_ip_map = UserIpMap.query()\ + c.user_ip_map = UserIpMap.query() \ .filter(UserIpMap.user == c.user).all() c.inherit_default_ips = c.user.inherit_default_permissions - c.default_user_ip_map = UserIpMap.query()\ + c.default_user_ip_map = UserIpMap.query() \ .filter(UserIpMap.user == User.get_default_user()).all() defaults = c.user.get_dict() @@ -451,9 +409,6 @@ force_defaults=False) def add_ip(self, id): - """POST /user_ips:Add an existing item""" - # url('user_ips', id=ID, method='put') - ip = request.POST.get('new_ip') user_model = UserModel() @@ -470,12 +425,10 @@ category='error') if 'default_user' in request.POST: - return redirect(url('admin_permissions_ips')) - return redirect(url('edit_user_ips', id=id)) + raise HTTPFound(location=url('admin_permissions_ips')) + raise HTTPFound(location=url('edit_user_ips', id=id)) def delete_ip(self, id): - """DELETE /user_ips_delete/id: Delete an existing item""" - # url('user_ips_delete', id=ID, method='delete') ip_id = request.POST.get('del_ip_id') user_model = UserModel() user_model.delete_extra_ip(id, ip_id) @@ -483,5 +436,5 @@ h.flash(_("Removed IP address from user whitelist"), category='success') if 'default_user' in request.POST: - return redirect(url('admin_permissions_ips')) - return redirect(url('edit_user_ips', id=id)) + raise HTTPFound(location=url('admin_permissions_ips')) + raise HTTPFound(location=url('edit_user_ips', id=id)) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/api/__init__.py --- a/kallithea/controllers/api/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/api/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -30,15 +30,18 @@ import types import traceback import time +import itertools from paste.response import replace_header from pylons.controllers import WSGIController +from pylons.controllers.util import Response +from pylons import request from webob.exc import HTTPError from kallithea.model.db import User from kallithea.model import meta -from kallithea.lib.compat import izip_longest, json +from kallithea.lib.compat import json from kallithea.lib.auth import AuthUser from kallithea.lib.base import _get_ip_addr as _get_ip, _get_access_path from kallithea.lib.utils2 import safe_unicode, safe_str @@ -56,20 +59,16 @@ return safe_str(self.message) -def jsonrpc_error(message, retid=None, code=None): +class JSONRPCErrorResponse(Response, Exception): """ Generate a Response object with a JSON-RPC error body + """ - :param code: - :param retid: - :param message: - """ - from pylons.controllers.util import Response - return Response( - body=json.dumps(dict(id=retid, result=None, error=message)), - status=code, - content_type='application/json' - ) + def __init__(self, message=None, retid=None, code=None): + Response.__init__(self, + body=json.dumps(dict(id=retid, result=None, error=message)), + status=code, + content_type='application/json') class JSONRPCController(WSGIController): @@ -103,6 +102,8 @@ """ try: return self._handle_request(environ, start_response) + except JSONRPCErrorResponse as e: + return e finally: meta.Session.remove() @@ -112,16 +113,16 @@ self._req_id = None if 'CONTENT_LENGTH' not in environ: log.debug("No Content-Length") - return jsonrpc_error(retid=self._req_id, - message="No Content-Length in request") + raise JSONRPCErrorResponse(retid=self._req_id, + message="No Content-Length in request") else: length = environ['CONTENT_LENGTH'] or 0 length = int(environ['CONTENT_LENGTH']) log.debug('Content-Length: %s', length) if length == 0: - return jsonrpc_error(retid=self._req_id, - message="Content-Length is 0") + raise JSONRPCErrorResponse(retid=self._req_id, + message="Content-Length is 0") raw_body = environ['wsgi.input'].read(length) @@ -129,9 +130,9 @@ json_body = json.loads(raw_body) except ValueError as e: # catch JSON errors Here - return jsonrpc_error(retid=self._req_id, - message="JSON parse error ERR:%s RAW:%r" - % (e, raw_body)) + raise JSONRPCErrorResponse(retid=self._req_id, + message="JSON parse error ERR:%s RAW:%r" + % (e, raw_body)) # check AUTH based on API key try: @@ -142,38 +143,36 @@ if not isinstance(self._request_params, dict): self._request_params = {} - log.debug( - 'method: %s, params: %s', self._req_method, - self._request_params - ) + log.debug('method: %s, params: %s', + self._req_method, self._request_params) except KeyError as e: - return jsonrpc_error(retid=self._req_id, - message='Incorrect JSON query missing %s' % e) + raise JSONRPCErrorResponse(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(retid=self._req_id, - message='Invalid API key') + raise JSONRPCErrorResponse(retid=self._req_id, + message='Invalid API key') auth_u = AuthUser(dbuser=u) if not AuthUser.check_ip_allowed(auth_u, ip_addr): - return jsonrpc_error(retid=self._req_id, - message='request from IP:%s not allowed' % (ip_addr,)) + raise JSONRPCErrorResponse(retid=self._req_id, + message='request from IP:%s not allowed' % (ip_addr,)) else: log.info('Access for IP:%s allowed', ip_addr) except Exception as e: - return jsonrpc_error(retid=self._req_id, - message='Invalid API key') + raise JSONRPCErrorResponse(retid=self._req_id, + message='Invalid API key') self._error = None try: self._func = self._find_method() except AttributeError as e: - return jsonrpc_error(retid=self._req_id, - message=str(e)) + raise JSONRPCErrorResponse(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 @@ -183,26 +182,18 @@ default_empty = types.NotImplementedType # kw arguments required by this method - func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults), - fillvalue=default_empty)) + func_kwargs = dict(itertools.izip_longest(reversed(arglist), reversed(defaults), + fillvalue=default_empty)) # this is little trick to inject logged in user for # perms decorators to work they expect the controller class to have # authuser attribute set - self.authuser = auth_u + self.authuser = request.user = auth_u # This attribute will need to be first param of a method that uses # api_key, which is translated to instance of user at that name USER_SESSION_ATTR = 'apiuser' - if USER_SESSION_ATTR not in arglist: - 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(): if arg == USER_SESSION_ATTR: @@ -213,14 +204,20 @@ # skip the required param check if it's default value is # NotImplementedType (default_empty) if default == default_empty and arg not in self._request_params: - return jsonrpc_error( + raise JSONRPCErrorResponse( retid=self._req_id, - message=( - 'Missing non optional `%s` arg in JSON DATA' % arg - ) + message='Missing non optional `%s` arg in JSON DATA' % arg, ) - self._rpc_args = {USER_SESSION_ATTR: u} + extra = set(self._request_params).difference(func_kwargs) + if extra: + raise JSONRPCErrorResponse( + retid=self._req_id, + message='Unknown %s arg in JSON DATA' % + ', '.join('`%s`' % arg for arg in extra), + ) + + self._rpc_args = {} self._rpc_args.update(self._request_params) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/api/api.py --- a/kallithea/controllers/api/api.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/api/api.py Sat Dec 24 00:34:38 2016 +0100 @@ -25,18 +25,16 @@ :license: GPLv3, see LICENSE.md for more details. """ - import time import traceback import logging from sqlalchemy import or_ -from kallithea import EXTERN_TYPE_INTERNAL from kallithea.controllers.api import JSONRPCController, JSONRPCError from kallithea.lib.auth import ( - PasswordGenerator, AuthUser, HasPermissionAllDecorator, - HasPermissionAnyDecorator, HasPermissionAnyApi, HasRepoPermissionAnyApi, - HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAny) + PasswordGenerator, AuthUser, HasPermissionAnyDecorator, + HasPermissionAnyDecorator, HasPermissionAny, HasRepoPermissionAny, + HasRepoGroupPermissionAny, HasUserGroupPermissionAny) from kallithea.lib.utils import map_groups, repo2db_mapper from kallithea.lib.utils2 import ( str2bool, time_to_datetime, safe_int, Optional, OAttr) @@ -49,7 +47,7 @@ from kallithea.model.gist import GistModel from kallithea.model.db import ( Repository, Setting, UserIpMap, Permission, User, Gist, - RepoGroup) + RepoGroup, UserGroup) from kallithea.lib.compat import json from kallithea.lib.exceptions import ( DefaultUserException, UserGroupsAssignedException) @@ -147,32 +145,28 @@ """ API Controller - Each method takes USER as first argument. This is then, based on given - API_KEY propagated as instance of user object who's making the call. + The authenticated user can be found as self.authuser. - example function:: + Example function:: - def func(apiuser,arg1, arg2,...): + def func(arg1, arg2,...): pass Each function should also **raise** JSONRPCError for any errors that happens. - """ - @HasPermissionAllDecorator('hg.admin') - def test(self, apiuser, args): + @HasPermissionAnyDecorator('hg.admin') + def test(self, args): return args - @HasPermissionAllDecorator('hg.admin') - def pull(self, apiuser, repoid): + @HasPermissionAnyDecorator('hg.admin') + def pull(self, repoid): """ Triggers a pull from remote location on given repo. Can be used to automatically keep remote repos up to date. This command can be executed only using api_key belonging to user with admin rights - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int @@ -210,16 +204,14 @@ 'Unable to pull changes from `%s`' % repo.repo_name ) - @HasPermissionAllDecorator('hg.admin') - def rescan_repos(self, apiuser, remove_obsolete=Optional(False)): + @HasPermissionAnyDecorator('hg.admin') + def rescan_repos(self, remove_obsolete=Optional(False)): """ Triggers rescan repositories action. If remove_obsolete is set than also delete repos that are in database but not in the filesystem. aka "clean zombies". This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param remove_obsolete: deletes repositories from database that are not found on the filesystem :type remove_obsolete: Optional(bool) @@ -254,14 +246,12 @@ 'Error occurred during rescan repositories action' ) - def invalidate_cache(self, apiuser, repoid): + def invalidate_cache(self, repoid): """ Invalidate cache for repository. This command can be executed only using api_key belonging to user with admin rights or regular user that have write or admin or write access to repository. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int @@ -284,11 +274,11 @@ """ repo = get_repo_or_error(repoid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo ! - if not HasRepoPermissionAnyApi('repository.admin', - 'repository.write')( - user=apiuser, repo_name=repo.repo_name): + if not HasRepoPermissionAny('repository.admin', + 'repository.write')( + repo_name=repo.repo_name): raise JSONRPCError('repository `%s` does not exist' % (repoid,)) try: @@ -304,7 +294,7 @@ ) # permission check inside - def lock(self, apiuser, repoid, locked=Optional(None), + def lock(self, repoid, locked=Optional(None), userid=Optional(OAttr('apiuser'))): """ Set locking state on given repository by given user. If userid param @@ -314,8 +304,6 @@ to user with admin rights or regular user that have admin or write access to repository. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param locked: lock state to be set @@ -350,14 +338,13 @@ """ repo = get_repo_or_error(repoid) - if HasPermissionAnyApi('hg.admin')(user=apiuser): + if HasPermissionAny('hg.admin')(): pass - elif HasRepoPermissionAnyApi('repository.admin', - 'repository.write')(user=apiuser, - repo_name=repo.repo_name): + elif HasRepoPermissionAny('repository.admin', + 'repository.write')(repo_name=repo.repo_name): # make sure normal user does not pass someone else userid, # he is not allowed to do that - if not isinstance(userid, Optional) and userid != apiuser.user_id: + if not isinstance(userid, Optional) and userid != self.authuser.user_id: raise JSONRPCError( 'userid is not the same as your user' ) @@ -365,7 +352,7 @@ raise JSONRPCError('repository `%s` does not exist' % (repoid,)) if isinstance(userid, Optional): - userid = apiuser.user_id + userid = self.authuser.user_id user = get_user_or_error(userid) @@ -423,14 +410,12 @@ 'Error occurred locking repository `%s`' % repo.repo_name ) - def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))): + def get_locks(self, userid=Optional(OAttr('apiuser'))): """ Get all repositories with locks for given userid, if this command is run by non-admin account userid is set to user who is calling this method, thus returning locks for himself. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param userid: User to get locks for :type userid: Optional(str or int) @@ -443,10 +428,10 @@ error : null """ - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # make sure normal user does not pass someone else userid, # he is not allowed to do that - if not isinstance(userid, Optional) and userid != apiuser.user_id: + if not isinstance(userid, Optional) and userid != self.authuser.user_id: raise JSONRPCError( 'userid is not the same as your user' ) @@ -458,7 +443,7 @@ user = get_user_or_error(userid) # show all locks - for r in Repository.getAll(): + for r in Repository.query(): userid, time_ = r.locked if time_: _api_data = r.get_api_data() @@ -471,8 +456,8 @@ return ret - @HasPermissionAllDecorator('hg.admin') - def get_ip(self, apiuser, userid=Optional(OAttr('apiuser'))): + @HasPermissionAnyDecorator('hg.admin') + def get_ip(self, userid=Optional(OAttr('apiuser'))): """ Shows IP address as seen from Kallithea server, together with all defined IP addresses for given user. If userid is not passed data is @@ -480,8 +465,6 @@ This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param userid: username to show ips for :type userid: Optional(str or int) @@ -501,7 +484,7 @@ """ if isinstance(userid, Optional): - userid = apiuser.user_id + userid = self.authuser.user_id user = get_user_or_error(userid) ips = UserIpMap.query().filter(UserIpMap.user == user).all() return dict( @@ -512,13 +495,11 @@ # alias for old show_ip = get_ip - @HasPermissionAllDecorator('hg.admin') - def get_server_info(self, apiuser): + @HasPermissionAnyDecorator('hg.admin') + def get_server_info(self): """ return server info, including Kallithea version and installed packages - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser OUTPUT:: @@ -533,7 +514,7 @@ """ return Setting.get_server_info() - def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))): + def get_user(self, userid=Optional(OAttr('apiuser'))): """ Gets a user by username or user_id, Returns empty result if user is not found. If userid param is skipped it is set to id of user who is @@ -541,8 +522,6 @@ belonging to user with admin rights, or regular users that cannot specify different userid than theirs - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param userid: user to get data for :type userid: Optional(str or int) @@ -577,30 +556,28 @@ error: null """ - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # make sure normal user does not pass someone else userid, # he is not allowed to do that - if not isinstance(userid, Optional) and userid != apiuser.user_id: + if not isinstance(userid, Optional) and userid != self.authuser.user_id: raise JSONRPCError( 'userid is not the same as your user' ) if isinstance(userid, Optional): - userid = apiuser.user_id + userid = self.authuser.user_id 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): + @HasPermissionAnyDecorator('hg.admin') + def get_users(self): """ Lists all existing users. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser OUTPUT:: @@ -617,18 +594,16 @@ result.append(user.get_api_data()) return result - @HasPermissionAllDecorator('hg.admin') - def create_user(self, apiuser, username, email, password=Optional(''), + @HasPermissionAnyDecorator('hg.admin') + def create_user(self, username, email, password=Optional(''), firstname=Optional(''), lastname=Optional(''), active=Optional(True), admin=Optional(False), - extern_name=Optional(EXTERN_TYPE_INTERNAL), - extern_type=Optional(EXTERN_TYPE_INTERNAL)): + extern_type=Optional(User.DEFAULT_AUTH_TYPE), + extern_name=Optional('')): """ Creates new user. Returns new user object. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param username: new username :type username: str or int :param email: email @@ -675,13 +650,9 @@ if User.get_by_username(username): raise JSONRPCError("user `%s` already exist" % (username,)) - if User.get_by_email(email, case_insensitive=True): + if User.get_by_email(email): raise JSONRPCError("email `%s` already exist" % (email,)) - if Optional.extract(extern_name): - # generate temporary password if user is external - password = PasswordGenerator().gen_password(length=8) - try: user = UserModel().create_or_update( username=Optional.extract(username), @@ -703,18 +674,16 @@ log.error(traceback.format_exc()) raise JSONRPCError('failed to create user `%s`' % (username,)) - @HasPermissionAllDecorator('hg.admin') - def update_user(self, apiuser, userid, username=Optional(None), - email=Optional(None),password=Optional(None), + @HasPermissionAnyDecorator('hg.admin') + def update_user(self, userid, username=Optional(None), + email=Optional(None), password=Optional(None), firstname=Optional(None), lastname=Optional(None), active=Optional(None), admin=Optional(None), - extern_type=Optional(None), extern_name=Optional(None),): + extern_type=Optional(None), extern_name=Optional(None)): """ updates given user if such user exists. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param userid: userid to update :type userid: str or int :param username: new username @@ -786,14 +755,12 @@ log.error(traceback.format_exc()) raise JSONRPCError('failed to update user `%s`' % (userid,)) - @HasPermissionAllDecorator('hg.admin') - def delete_user(self, apiuser, userid): + @HasPermissionAnyDecorator('hg.admin') + def delete_user(self, userid): """ deletes given user if such user exists. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param userid: user to delete :type userid: str or int @@ -831,14 +798,12 @@ % (user.user_id, user.username)) # permission check inside - def get_user_group(self, apiuser, usergroupid): + def get_user_group(self, usergroupid): """ Gets an existing user group. This command can be executed only using api_key belonging to user with admin rights or user who has at least read access to user group. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param usergroupid: id of user_group to edit :type usergroupid: str or int @@ -856,25 +821,23 @@ """ user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have at least read permission for this user group ! _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) data = user_group.get_api_data() return data # permission check inside - def get_user_groups(self, apiuser): + def get_user_groups(self): """ Lists all existing user groups. This command can be executed only using api_key belonging to user with admin rights or user who has at least read access to user group. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser OUTPUT:: @@ -885,22 +848,19 @@ result = [] _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) - extras = {'user': apiuser} - for user_group in UserGroupList(UserGroupModel().get_all(), - perm_set=_perms, extra_kwargs=extras): + for user_group in UserGroupList(UserGroup.query().all(), + perm_set=_perms): result.append(user_group.get_api_data()) return result @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true') - def create_user_group(self, apiuser, group_name, description=Optional(''), + def create_user_group(self, group_name, description=Optional(''), owner=Optional(OAttr('apiuser')), active=Optional(True)): """ Creates new user group. This command can be executed only using api_key belonging to user with admin rights or an user who has create user group permission - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param group_name: name of new user group :type group_name: str :param description: group description @@ -936,7 +896,7 @@ try: if isinstance(owner, Optional): - owner = apiuser.user_id + owner = self.authuser.user_id owner = get_user_or_error(owner) active = Optional.extract(active) @@ -953,15 +913,13 @@ raise JSONRPCError('failed to create group `%s`' % (group_name,)) # permission check inside - def update_user_group(self, apiuser, usergroupid, group_name=Optional(''), + def update_user_group(self, usergroupid, group_name=Optional(''), description=Optional(''), owner=Optional(None), active=Optional(True)): """ Updates given usergroup. This command can be executed only using api_key belonging to user with admin rights or an admin of given user group - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param usergroupid: id of user group to update :type usergroupid: str or int :param group_name: name of new user group @@ -992,11 +950,11 @@ """ user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this user group ! _perms = ('usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) if not isinstance(owner, Optional): @@ -1005,7 +963,7 @@ updates = {} store_update(updates, group_name, 'users_group_name') store_update(updates, description, 'user_group_description') - store_update(updates, owner, 'user') + store_update(updates, owner, 'owner') store_update(updates, active, 'users_group_active') try: UserGroupModel().update(user_group, updates) @@ -1020,14 +978,12 @@ raise JSONRPCError('failed to update user group `%s`' % (usergroupid,)) # permission check inside - def delete_user_group(self, apiuser, usergroupid): + def delete_user_group(self, usergroupid): """ Delete given user group by user group id or name. This command can be executed only using api_key belonging to user with admin rights or an admin of given user group - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param usergroupid: :type usergroupid: int @@ -1051,11 +1007,11 @@ """ user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this user group ! _perms = ('usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: @@ -1074,17 +1030,15 @@ raise JSONRPCError('failed to delete user group ID:%s %s' % (user_group.users_group_id, user_group.users_group_name) - ) + ) # permission check inside - def add_user_to_user_group(self, apiuser, usergroupid, userid): + def add_user_to_user_group(self, usergroupid, userid): """ Adds a user to a user group. If user exists in that group success will be `false`. This command can be executed only using api_key belonging to user with admin rights or an admin of given user group - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param usergroupid: :type usergroupid: int :param userid: @@ -1112,11 +1066,11 @@ """ user = get_user_or_error(userid) user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this user group ! _perms = ('usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: @@ -1141,14 +1095,12 @@ ) # permission check inside - def remove_user_from_user_group(self, apiuser, usergroupid, userid): + def remove_user_from_user_group(self, usergroupid, userid): """ Removes a user from a user group. If user is not in given group success will be `false`. This command can be executed only using api_key belonging to user with admin rights or an admin of given user group - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param usergroupid: :param userid: @@ -1166,11 +1118,11 @@ """ user = get_user_or_error(userid) user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this user group ! _perms = ('usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: @@ -1190,15 +1142,13 @@ ) # permission check inside - def get_repo(self, apiuser, repoid): + def get_repo(self, repoid): """ Gets an existing repository by it's name or repository_id. Members will return either users_group or user associated to that repository. This command can be executed only using api_key belonging to user with admin rights or regular user that have at least read access to repository. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int @@ -1251,10 +1201,10 @@ """ repo = get_repo_or_error(repoid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo ! perms = ('repository.admin', 'repository.write', 'repository.read') - if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name): + if not HasRepoPermissionAny(*perms)(repo_name=repo.repo_name): raise JSONRPCError('repository `%s` does not exist' % (repoid,)) members = [] @@ -1288,14 +1238,12 @@ return data # permission check inside - def get_repos(self, apiuser): + def get_repos(self): """ Lists all existing repositories. This command can be executed only using api_key belonging to user with admin rights or regular user that have admin, write or read access to repository. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser OUTPUT:: @@ -1321,17 +1269,17 @@ error: null """ result = [] - if not HasPermissionAnyApi('hg.admin')(user=apiuser): - repos = RepoModel().get_all_user_repos(user=apiuser) + if not HasPermissionAny('hg.admin')(): + repos = RepoModel().get_all_user_repos(user=self.authuser.user_id) else: - repos = RepoModel().get_all() + repos = Repository.query() for repo in repos: result.append(repo.get_api_data()) return result # permission check inside - def get_repo_nodes(self, apiuser, repoid, revision, root_path, + def get_repo_nodes(self, repoid, revision, root_path, ret_type=Optional('all')): """ returns a list of nodes and it's children in a flat list for a given path @@ -1339,8 +1287,6 @@ `dirs`. This command can be executed only using api_key belonging to user with admin rights or regular user that have at least read access to repository. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param revision: revision for which listing should be done @@ -1365,10 +1311,10 @@ """ repo = get_repo_or_error(repoid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo ! perms = ('repository.admin', 'repository.write', 'repository.read') - if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name): + if not HasRepoPermissionAny(*perms)(repo_name=repo.repo_name): raise JSONRPCError('repository `%s` does not exist' % (repoid,)) ret_type = Optional.extract(ret_type) @@ -1392,7 +1338,7 @@ ) @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') - def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')), + def create_repo(self, repo_name, owner=Optional(OAttr('apiuser')), repo_type=Optional('hg'), description=Optional(''), private=Optional(False), clone_uri=Optional(None), landing_rev=Optional('rev:tip'), @@ -1408,8 +1354,6 @@ belonging to user with admin rights or regular user that have create repository permission. Regular users cannot specify owner parameter - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repo_name: repository name :type repo_name: str :param owner: user_id or username @@ -1453,14 +1397,14 @@ } """ - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): if not isinstance(owner, Optional): - #forbid setting owner for non-admins + # forbid setting owner for non-admins raise JSONRPCError( 'Only Kallithea admin can specify `owner` param' ) if isinstance(owner, Optional): - owner = apiuser.user_id + owner = self.authuser.user_id owner = get_user_or_error(owner) @@ -1505,10 +1449,7 @@ ) task = RepoModel().create(form_data=data, cur_user=owner) - from celery.result import BaseAsyncResult - task_id = None - if isinstance(task, BaseAsyncResult): - task_id = task.task_id + task_id = task.task_id # no commit, it's done in RepoModel, or async via celery return dict( msg="Created new repository `%s`" % (repo_name,), @@ -1522,7 +1463,7 @@ 'failed to create repository `%s`' % (repo_name,)) # permission check inside - def update_repo(self, apiuser, repoid, name=Optional(None), + def update_repo(self, repoid, name=Optional(None), owner=Optional(OAttr('apiuser')), group=Optional(None), description=Optional(''), private=Optional(False), @@ -1534,8 +1475,6 @@ """ Updates repo - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param name: @@ -1550,19 +1489,18 @@ :param enable_downloads: """ repo = get_repo_or_error(repoid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo ! - if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser, - repo_name=repo.repo_name): + if not HasRepoPermissionAny('repository.admin')(repo_name=repo.repo_name): raise JSONRPCError('repository `%s` does not exist' % (repoid,)) if (name != repo.repo_name and - not HasPermissionAnyApi('hg.create.repository')(user=apiuser) + not HasPermissionAny('hg.create.repository')() ): raise JSONRPCError('no permission to create (or move) repositories') if not isinstance(owner, Optional): - #forbid setting owner for non-admins + # forbid setting owner for non-admins raise JSONRPCError( 'Only Kallithea admin can specify `owner` param' ) @@ -1575,7 +1513,7 @@ try: store_update(updates, name, 'repo_name') store_update(updates, repo_group, 'repo_group') - store_update(updates, owner, 'user') + store_update(updates, owner, 'owner') store_update(updates, description, 'repo_description') store_update(updates, private, 'repo_private') store_update(updates, clone_uri, 'clone_uri') @@ -1595,7 +1533,7 @@ raise JSONRPCError('failed to update repo `%s`' % repoid) @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') - def fork_repo(self, apiuser, repoid, fork_name, + def fork_repo(self, repoid, fork_name, owner=Optional(OAttr('apiuser')), description=Optional(''), copy_permissions=Optional(False), private=Optional(False), landing_rev=Optional('rev:tip')): @@ -1606,8 +1544,6 @@ user with admin rights or regular user that have fork permission, and at least read access to forking repository. Regular users cannot specify owner parameter. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param fork_name: @@ -1650,25 +1586,24 @@ type_ = 'fork' if _repo.fork else 'repo' raise JSONRPCError("%s `%s` already exist" % (type_, fork_name)) - if HasPermissionAnyApi('hg.admin')(user=apiuser): + if HasPermissionAny('hg.admin')(): pass - elif HasRepoPermissionAnyApi('repository.admin', - 'repository.write', - 'repository.read')(user=apiuser, - repo_name=repo.repo_name): + elif HasRepoPermissionAny('repository.admin', + 'repository.write', + 'repository.read')(repo_name=repo.repo_name): if not isinstance(owner, Optional): - #forbid setting owner for non-admins + # forbid setting owner for non-admins raise JSONRPCError( 'Only Kallithea admin can specify `owner` param' ) - if not HasPermissionAnyApi('hg.create.repository')(user=apiuser): + if not HasPermissionAny('hg.create.repository')(): raise JSONRPCError('no permission to create repositories') else: raise JSONRPCError('repository `%s` does not exist' % (repoid,)) if isinstance(owner, Optional): - owner = apiuser.user_id + owner = self.authuser.user_id owner = get_user_or_error(owner) @@ -1691,10 +1626,7 @@ ) task = RepoModel().create_fork(form_data, cur_user=owner) # no commit, it's done in RepoModel, or async via celery - from celery.result import BaseAsyncResult - task_id = None - if isinstance(task, BaseAsyncResult): - task_id = task.task_id + task_id = task.task_id return dict( msg='Created fork of `%s` as `%s`' % (repo.repo_name, fork_name), @@ -1710,15 +1642,13 @@ ) # permission check inside - def delete_repo(self, apiuser, repoid, forks=Optional('')): + def delete_repo(self, repoid, forks=Optional('')): """ Deletes a repository. This command can be executed only using api_key belonging to user with admin rights or regular user that have admin access to repository. When `forks` param is set it's possible to detach or delete forks of deleting repository - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param forks: `detach` or `delete`, what do do with attached forks for repo @@ -1736,10 +1666,9 @@ """ repo = get_repo_or_error(repoid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo ! - if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser, - repo_name=repo.repo_name): + if not HasRepoPermissionAny('repository.admin')(repo_name=repo.repo_name): raise JSONRPCError('repository `%s` does not exist' % (repoid,)) try: @@ -1768,15 +1697,13 @@ 'failed to delete repository `%s`' % (repo.repo_name,) ) - @HasPermissionAllDecorator('hg.admin') - def grant_user_permission(self, apiuser, repoid, userid, perm): + @HasPermissionAnyDecorator('hg.admin') + def grant_user_permission(self, repoid, userid, perm): """ Grant permission for user on given repository, or update existing one if found. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param userid: @@ -1815,14 +1742,12 @@ ) ) - @HasPermissionAllDecorator('hg.admin') - def revoke_user_permission(self, apiuser, repoid, userid): + @HasPermissionAnyDecorator('hg.admin') + def revoke_user_permission(self, repoid, userid): """ Revoke permission for user on given repository. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param userid: @@ -1858,14 +1783,12 @@ ) # permission check inside - def grant_user_group_permission(self, apiuser, repoid, usergroupid, perm): + def grant_user_group_permission(self, repoid, usergroupid, perm): """ Grant permission for user group on given repository, or update existing one if found. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param usergroupid: id of usergroup @@ -1895,17 +1818,17 @@ repo = get_repo_or_error(repoid) perm = get_perm_or_error(perm) user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo ! _perms = ('repository.admin',) - if not HasRepoPermissionAnyApi(*_perms)( - user=apiuser, repo_name=repo.repo_name): + if not HasRepoPermissionAny(*_perms)( + repo_name=repo.repo_name): raise JSONRPCError('repository `%s` does not exist' % (repoid,)) # check if we have at least read permission for this user group ! _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: @@ -1931,13 +1854,11 @@ ) # permission check inside - def revoke_user_group_permission(self, apiuser, repoid, usergroupid): + def revoke_user_group_permission(self, repoid, usergroupid): """ Revoke permission for user group on given repository. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repoid: repository name or repository id :type repoid: str or int :param usergroupid: @@ -1953,17 +1874,17 @@ """ repo = get_repo_or_error(repoid) user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo ! _perms = ('repository.admin',) - if not HasRepoPermissionAnyApi(*_perms)( - user=apiuser, repo_name=repo.repo_name): + if not HasRepoPermissionAny(*_perms)( + repo_name=repo.repo_name): raise JSONRPCError('repository `%s` does not exist' % (repoid,)) # check if we have at least read permission for this user group ! _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError('user group `%s` does not exist' % (usergroupid,)) try: @@ -1986,14 +1907,12 @@ ) ) - @HasPermissionAllDecorator('hg.admin') - def get_repo_group(self, apiuser, repogroupid): + @HasPermissionAnyDecorator('hg.admin') + def get_repo_group(self, repogroupid): """ Returns given repo group together with permissions, and repositories inside the group - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repogroupid: id/name of repository group :type repogroupid: str or int """ @@ -2024,21 +1943,19 @@ data["members"] = members return data - @HasPermissionAllDecorator('hg.admin') - def get_repo_groups(self, apiuser): + @HasPermissionAnyDecorator('hg.admin') + def get_repo_groups(self): """ Returns all repository groups - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser """ result = [] - for repo_group in RepoGroupModel().get_all(): + for repo_group in RepoGroup.query(): result.append(repo_group.get_api_data()) return result - @HasPermissionAllDecorator('hg.admin') - def create_repo_group(self, apiuser, group_name, description=Optional(''), + @HasPermissionAnyDecorator('hg.admin') + def create_repo_group(self, group_name, description=Optional(''), owner=Optional(OAttr('apiuser')), parent=Optional(None), copy_permissions=Optional(False)): @@ -2046,8 +1963,6 @@ Creates a repository group. This command can be executed only using api_key belonging to user with admin rights. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param group_name: :type group_name: :param description: @@ -2081,7 +1996,7 @@ raise JSONRPCError("repo group `%s` already exist" % (group_name,)) if isinstance(owner, Optional): - owner = apiuser.user_id + owner = self.authuser.user_id group_description = Optional.extract(description) parent_group = Optional.extract(parent) if not isinstance(parent, Optional): @@ -2106,8 +2021,8 @@ log.error(traceback.format_exc()) raise JSONRPCError('failed to create repo group `%s`' % (group_name,)) - @HasPermissionAllDecorator('hg.admin') - def update_repo_group(self, apiuser, repogroupid, group_name=Optional(''), + @HasPermissionAnyDecorator('hg.admin') + def update_repo_group(self, repogroupid, group_name=Optional(''), description=Optional(''), owner=Optional(OAttr('apiuser')), parent=Optional(None), enable_locking=Optional(False)): @@ -2132,12 +2047,10 @@ raise JSONRPCError('failed to update repository group `%s`' % (repogroupid,)) - @HasPermissionAllDecorator('hg.admin') - def delete_repo_group(self, apiuser, repogroupid): + @HasPermissionAnyDecorator('hg.admin') + def delete_repo_group(self, repogroupid): """ - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repogroupid: name or id of repository group :type repogroupid: str or int @@ -2173,10 +2086,10 @@ log.error(traceback.format_exc()) raise JSONRPCError('failed to delete repo group ID:%s %s' % (repo_group.group_id, repo_group.group_name) - ) + ) # permission check inside - def grant_user_permission_to_repo_group(self, apiuser, repogroupid, userid, + def grant_user_permission_to_repo_group(self, repogroupid, userid, perm, apply_to_children=Optional('none')): """ Grant permission for user on given repository group, or update existing @@ -2184,8 +2097,6 @@ to user with admin rights, or user who has admin right to given repository group. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repogroupid: name or id of repository group :type repogroupid: str or int :param userid: @@ -2215,10 +2126,9 @@ repo_group = get_repo_group_or_error(repogroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo group ! - if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser, - group_name=repo_group.group_name): + if not HasRepoGroupPermissionAny('group.admin')(group_name=repo_group.group_name): raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,)) user = get_user_or_error(userid) @@ -2245,15 +2155,13 @@ userid, repo_group.name)) # permission check inside - def revoke_user_permission_from_repo_group(self, apiuser, repogroupid, userid, + def revoke_user_permission_from_repo_group(self, repogroupid, userid, apply_to_children=Optional('none')): """ Revoke permission for user on given repository group. This command can be executed only using api_key belonging to user with admin rights, or user who has admin right to given repository group. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repogroupid: name or id of repository group :type repogroupid: str or int :param userid: @@ -2282,10 +2190,9 @@ repo_group = get_repo_group_or_error(repogroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo group ! - if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser, - group_name=repo_group.group_name): + if not HasRepoGroupPermissionAny('group.admin')(group_name=repo_group.group_name): raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,)) user = get_user_or_error(userid) @@ -2312,16 +2219,14 @@ # permission check inside def grant_user_group_permission_to_repo_group( - self, apiuser, repogroupid, usergroupid, perm, - apply_to_children=Optional('none'),): + self, repogroupid, usergroupid, perm, + apply_to_children=Optional('none')): """ Grant permission for user group on given repository group, or update existing one if found. This command can be executed only using api_key belonging to user with admin rights, or user who has admin right to given repository group. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repogroupid: name or id of repository group :type repogroupid: str or int :param usergroupid: id of usergroup @@ -2353,18 +2258,18 @@ repo_group = get_repo_group_or_error(repogroupid) perm = get_perm_or_error(perm, prefix='group.') user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo group ! _perms = ('group.admin',) - if not HasRepoGroupPermissionAnyApi(*_perms)( - user=apiuser, group_name=repo_group.group_name): + if not HasRepoGroupPermissionAny(*_perms)( + group_name=repo_group.group_name): raise JSONRPCError( 'repository group `%s` does not exist' % (repogroupid,)) # check if we have at least read permission for this user group ! _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError( 'user group `%s` does not exist' % (usergroupid,)) @@ -2379,9 +2284,9 @@ Session().commit() return dict( msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % ( - perm.permission_name, apply_to_children, - user_group.users_group_name, repo_group.name - ), + perm.permission_name, apply_to_children, + user_group.users_group_name, repo_group.name + ), success=True ) except Exception: @@ -2395,15 +2300,13 @@ # permission check inside def revoke_user_group_permission_from_repo_group( - self, apiuser, repogroupid, usergroupid, + self, repogroupid, usergroupid, apply_to_children=Optional('none')): """ Revoke permission for user group on given repository. This command can be executed only using api_key belonging to user with admin rights, or user who has admin right to given repository group. - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param repogroupid: name or id of repository group :type repogroupid: str or int :param usergroupid: @@ -2431,18 +2334,18 @@ """ repo_group = get_repo_group_or_error(repogroupid) user_group = get_user_group_or_error(usergroupid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # check if we have admin permission for this repo group ! _perms = ('group.admin',) - if not HasRepoGroupPermissionAnyApi(*_perms)( - user=apiuser, group_name=repo_group.group_name): + if not HasRepoGroupPermissionAny(*_perms)( + group_name=repo_group.group_name): raise JSONRPCError( 'repository group `%s` does not exist' % (repogroupid,)) # check if we have at least read permission for this user group ! _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',) if not HasUserGroupPermissionAny(*_perms)( - user=apiuser, user_group_name=user_group.users_group_name): + user_group_name=user_group.users_group_name): raise JSONRPCError( 'user group `%s` does not exist' % (usergroupid,)) @@ -2468,62 +2371,56 @@ ) ) - def get_gist(self, apiuser, gistid): + def get_gist(self, gistid): """ Get given gist by id - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param gistid: id of private or public gist :type gistid: str """ gist = get_gist_or_error(gistid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): - if gist.gist_owner != apiuser.user_id: + if not HasPermissionAny('hg.admin')(): + if gist.owner_id != self.authuser.user_id: raise JSONRPCError('gist `%s` does not exist' % (gistid,)) return gist.get_api_data() - def get_gists(self, apiuser, userid=Optional(OAttr('apiuser'))): + def get_gists(self, userid=Optional(OAttr('apiuser'))): """ Get all gists for given user. If userid is empty returned gists are for user who called the api - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param userid: user to get gists for :type userid: Optional(str or int) """ - if not HasPermissionAnyApi('hg.admin')(user=apiuser): + if not HasPermissionAny('hg.admin')(): # make sure normal user does not pass someone else userid, # he is not allowed to do that - if not isinstance(userid, Optional) and userid != apiuser.user_id: + if not isinstance(userid, Optional) and userid != self.authuser.user_id: raise JSONRPCError( 'userid is not the same as your user' ) if isinstance(userid, Optional): - user_id = apiuser.user_id + user_id = self.authuser.user_id else: user_id = get_user_or_error(userid).user_id gists = [] - _gists = Gist().query()\ - .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\ - .filter(Gist.gist_owner == user_id)\ + _gists = Gist().query() \ + .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \ + .filter(Gist.owner_id == user_id) \ .order_by(Gist.created_on.desc()) for gist in _gists: gists.append(gist.get_api_data()) return gists - def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')), + def create_gist(self, files, owner=Optional(OAttr('apiuser')), gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1), description=Optional('')): """ Creates new Gist - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param files: files to be added to gist {'filename': {'content':'...', 'lexer': null}, 'filename2': {'content':'...', 'lexer': null}} @@ -2557,7 +2454,7 @@ """ try: if isinstance(owner, Optional): - owner = apiuser.user_id + owner = self.authuser.user_id owner = get_user_or_error(owner) description = Optional.extract(description) @@ -2578,19 +2475,17 @@ log.error(traceback.format_exc()) raise JSONRPCError('failed to create gist') - # def update_gist(self, apiuser, gistid, files, owner=Optional(OAttr('apiuser')), + # def update_gist(self, gistid, files, owner=Optional(OAttr('apiuser')), # gist_type=Optional(Gist.GIST_PUBLIC), # gist_lifetime=Optional(-1), gist_description=Optional('')): # gist = get_gist_or_error(gistid) # updates = {} # permission check inside - def delete_gist(self, apiuser, gistid): + def delete_gist(self, gistid): """ Deletes existing gist - :param apiuser: filled automatically from apikey - :type apiuser: AuthUser :param gistid: id of gist to delete :type gistid: str @@ -2613,8 +2508,8 @@ """ gist = get_gist_or_error(gistid) - if not HasPermissionAnyApi('hg.admin')(user=apiuser): - if gist.gist_owner != apiuser.user_id: + if not HasPermissionAny('hg.admin')(): + if gist.owner_id != self.authuser.user_id: raise JSONRPCError('gist `%s` does not exist' % (gistid,)) try: diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/bookmarks.py --- a/kallithea/controllers/bookmarks.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.controllers.bookmarks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Bookmarks controller for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Dec 1, 2011 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import logging - -from pylons import tmpl_context as c - -from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator -from kallithea.lib.base import BaseRepoController, render -from kallithea.lib.compat import OrderedDict -from webob.exc import HTTPNotFound - -log = logging.getLogger(__name__) - - -class BookmarksController(BaseRepoController): - - def __before__(self): - super(BookmarksController, self).__before__() - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def index(self): - if c.db_repo_scm_instance.alias != 'hg': - raise HTTPNotFound() - - c.repo_bookmarks = OrderedDict() - - bookmarks = [(name, c.db_repo_scm_instance.get_changeset(hash_)) for \ - name, hash_ in c.db_repo_scm_instance._repo._bookmarks.items()] - ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True) - for name, cs_book in ordered_tags: - c.repo_bookmarks[name] = cs_book - - return render('bookmarks/bookmarks.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/branches.py --- a/kallithea/controllers/branches.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.controllers.branches -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -branches controller for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 21, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import logging -import binascii - -from pylons import tmpl_context as c - -from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator -from kallithea.lib.base import BaseRepoController, render -from kallithea.lib.compat import OrderedDict -from kallithea.lib.utils2 import safe_unicode - -log = logging.getLogger(__name__) - - -class BranchesController(BaseRepoController): - - def __before__(self): - super(BranchesController, self).__before__() - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def index(self): - - def _branchtags(localrepo): - bt_closed = {} - for bn, heads in localrepo.branchmap().iteritems(): - tip = heads[-1] - if 'close' in localrepo.changelog.read(tip)[5]: - bt_closed[bn] = tip - return bt_closed - - cs_g = c.db_repo_scm_instance.get_changeset - - c.repo_closed_branches = {} - if c.db_repo.repo_type == 'hg': - bt_closed = _branchtags(c.db_repo_scm_instance._repo) - _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) - for n, h in bt_closed.items()] - - c.repo_closed_branches = OrderedDict(sorted(_closed_branches, - key=lambda ctx: ctx[0], - reverse=False)) - - _branches = [(safe_unicode(n), cs_g(h)) - for n, h in c.db_repo_scm_instance.branches.items()] - c.repo_branches = OrderedDict(sorted(_branches, - key=lambda ctx: ctx[0], - reverse=False)) - - return render('branches/branches.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/changelog.py --- a/kallithea/controllers/changelog.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/changelog.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,18 +28,18 @@ import logging import traceback -from pylons import request, url, session, tmpl_context as c -from pylons.controllers.util import redirect +from pylons import request, session, tmpl_context as c from pylons.i18n.translation import _ -from webob.exc import HTTPNotFound, HTTPBadRequest +from webob.exc import HTTPFound, HTTPNotFound, HTTPBadRequest import kallithea.lib.helpers as h +from kallithea.config.routing import url from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from kallithea.lib.base import BaseRepoController, render -from kallithea.lib.helpers import RepoPage from kallithea.lib.compat import json from kallithea.lib.graphmod import graph_data -from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError,\ +from kallithea.lib.page import RepoPage +from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \ ChangesetError, NodeDoesNotExistError, EmptyRepositoryError from kallithea.lib.utils2 import safe_int, safe_str @@ -48,6 +48,7 @@ def _load_changelog_summary(): + # also used from summary ... p = safe_int(request.GET.get('page'), 1) size = safe_int(request.GET.get('size'), 10) @@ -99,8 +100,8 @@ if request.GET.get('set'): request.GET.pop('set', None) if revision is None: - return redirect(url('changelog_home', repo_name=repo_name, **request.GET)) - return redirect(url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET)) + raise HTTPFound(location=url('changelog_home', repo_name=repo_name, **request.GET)) + raise HTTPFound(location=url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET)) limit = 2000 default = 100 @@ -112,13 +113,13 @@ c.size = int(session.get('changelog_size', default)) # min size must be 1 c.size = max(c.size, 1) - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) branch_name = request.GET.get('branch', None) if (branch_name and branch_name not in c.db_repo_scm_instance.branches and branch_name not in c.db_repo_scm_instance.closed_branches and not revision): - return redirect(url('changelog_file_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changelog_file_home', repo_name=c.repo_name, revision=branch_name, f_path=f_path or '')) if revision == 'tip': @@ -140,7 +141,7 @@ collection = cs.get_file_history(f_path) except RepositoryError as e: h.flash(safe_str(e), category='warning') - redirect(h.url('changelog_home', repo_name=repo_name)) + raise HTTPFound(location=h.url('changelog_home', repo_name=repo_name)) collection = list(reversed(collection)) else: collection = c.db_repo_scm_instance.get_changesets(start=0, end=revision, @@ -155,11 +156,11 @@ c.statuses = c.db_repo.statuses(page_revisions) except EmptyRepositoryError as e: h.flash(safe_str(e), category='warning') - return redirect(url('summary_home', repo_name=c.repo_name)) + raise HTTPFound(location=url('summary_home', repo_name=c.repo_name)) except (RepositoryError, ChangesetDoesNotExistError, Exception) as e: log.error(traceback.format_exc()) h.flash(safe_str(e), category='error') - return redirect(url('changelog_home', repo_name=c.repo_name)) + raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name)) c.branch_name = branch_name c.branch_filters = [('', _('None'))] + \ diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/changeset.py --- a/kallithea/controllers/changeset.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/changeset.py Sat Dec 24 00:34:38 2016 +0100 @@ -15,8 +15,7 @@ kallithea.controllers.changeset ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -changeset controller for pylons showing changes between -revisions +changeset controller showing changes between revisions This file was forked by the Kallithea project in July 2014. Original author and date, and relevant copyright and licensing information is below: @@ -29,19 +28,18 @@ import logging import traceback from collections import defaultdict -from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound from pylons import tmpl_context as c, request, response from pylons.i18n.translation import _ -from pylons.controllers.util import redirect -from kallithea.lib.utils import jsonify +from webob.exc import HTTPFound, HTTPForbidden, HTTPBadRequest, HTTPNotFound +from kallithea.lib.utils import jsonify from kallithea.lib.vcs.exceptions import RepositoryError, \ - ChangesetDoesNotExistError + ChangesetDoesNotExistError, EmptyRepositoryError from kallithea.lib.compat import json import kallithea.lib.helpers as h -from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\ +from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ NotAnonymous from kallithea.lib.base import BaseRepoController, render from kallithea.lib.utils import action_logger @@ -173,6 +171,27 @@ return h.link_to(icon, h.url.current(**params), title=lbl, class_='tooltip') +# Could perhaps be nice to have in the model but is too high level ... +def create_comment(text, status, f_path, line_no, revision=None, pull_request_id=None, closing_pr=None): + """Comment functionality shared between changesets and pullrequests""" + f_path = f_path or None + line_no = line_no or None + + comment = ChangesetCommentsModel().create( + text=text, + repo=c.db_repo.repo_id, + author=c.authuser.user_id, + revision=revision, + pull_request=pull_request_id, + f_path=f_path, + line_no=line_no, + status_change=ChangesetStatus.get_status_lbl(status) if status else None, + closing_pr=closing_pr, + ) + + return comment + + class ChangesetController(BaseRepoController): def __before__(self): @@ -185,6 +204,7 @@ c.user_groups_array = repo_model.get_user_groups_js() def _index(self, revision, method): + c.pull_request = None c.anchor_url = anchor_url c.ignorews_url = _ignorews_url c.context_url = _context_url @@ -207,7 +227,7 @@ if not c.cs_ranges: raise RepositoryError('Changeset range returned empty result') - except(ChangesetDoesNotExistError,), e: + except (ChangesetDoesNotExistError, EmptyRepositoryError): log.debug(traceback.format_exc()) msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') @@ -226,7 +246,6 @@ # Iterate over ranges (default changeset view is always one changeset) for changeset in c.cs_ranges: - inlines = [] if method == 'show': c.statuses.extend([ChangesetStatusModel().get_status( c.db_repo.repo_id, changeset.raw_id)]) @@ -238,23 +257,21 @@ revision=changeset.raw_id)) # Status change comments - mostly from pull requests - comments.update((st.changeset_comment_id, st.comment) + comments.update((st.comment_id, st.comment) for st in ChangesetStatusModel() .get_statuses(c.db_repo.repo_id, changeset.raw_id, with_revisions=True) - if st.changeset_comment_id is not None) + if st.comment_id is not None) - inlines = ChangesetCommentsModel()\ + inlines = ChangesetCommentsModel() \ .get_inline_comments(c.db_repo.repo_id, revision=changeset.raw_id) c.inline_comments.extend(inlines) - c.changes[changeset.raw_id] = [] - cs2 = changeset.raw_id cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset().raw_id context_lcl = get_line_ctx('', request.GET) - ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET) + ign_whitespace_lcl = get_ignore_ws('', request.GET) _diff = c.db_repo_scm_instance.get_diff(cs1, cs2, ignore_whitespace=ign_whitespace_lcl, context=context_lcl) @@ -263,7 +280,7 @@ vcs=c.db_repo_scm_instance.alias, format='gitdiff', diff_limit=diff_limit) - cs_changes = OrderedDict() + file_diff_data = [] if method == 'show': _parsed = diff_processor.prepare() c.limited_diff = False @@ -273,16 +290,17 @@ st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] - fid = h.FID(changeset.raw_id, f['filename']) + filename = f['filename'] + fid = h.FID(changeset.raw_id, filename) + url_fid = h.FID('', filename) diff = diff_processor.as_html(enable_comments=enable_comments, parsed_lines=[f]) - cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'], - diff, st] + file_diff_data.append((fid, url_fid, f['operation'], f['old_filename'], filename, diff, st)) else: # downloads/raw we only need RAW diff nothing else diff = diff_processor.as_raw() - cs_changes[''] = [None, None, None, None, diff, None] - c.changes[changeset.raw_id] = cs_changes + file_diff_data.append(('', None, None, None, diff, None)) + c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data) #sort comments in creation order c.comments = [com for com_id, com in sorted(comments.items())] @@ -349,57 +367,47 @@ 'repository.admin') @jsonify def comment(self, repo_name, revision): + assert request.environ.get('HTTP_X_PARTIAL_XHR') + status = request.POST.get('changeset_status') text = request.POST.get('text', '').strip() - c.comment = comment = ChangesetCommentsModel().create( - text=text, - repo=c.db_repo.repo_id, - user=c.authuser.user_id, + c.comment = create_comment( + text, + status, revision=revision, f_path=request.POST.get('f_path'), line_no=request.POST.get('line'), - status_change=(ChangesetStatus.get_status_lbl(status) - if status else None) ) # get status if set ! if status: # if latest status was from pull request and it's closed - # disallow changing status ! - # dont_allow_on_closed_pull_request = True ! - + # disallow changing status ! RLY? try: ChangesetStatusModel().set_status( c.db_repo.repo_id, status, c.authuser.user_id, - comment, + c.comment, revision=revision, - dont_allow_on_closed_pull_request=True + dont_allow_on_closed_pull_request=True, ) except StatusChangeOnClosedPullRequestError: - log.debug(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)) + log.debug('cannot change status on %s with closed pull request', revision) + raise HTTPBadRequest() + action_logger(self.authuser, 'user_commented_revision:%s' % revision, c.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)) - #only ajax below data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), } - if comment is not None: - data.update(comment.get_dict()) + if c.comment is not None: + data.update(c.comment.get_dict()) data.update({'rendered_text': render('changeset/changeset_comment_block.html')}) @@ -409,24 +417,12 @@ @NotAnonymous() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') - def preview_comment(self): - if not request.environ.get('HTTP_X_PARTIAL_XHR'): - raise HTTPBadRequest() - text = request.POST.get('text') - if text: - return h.rst_w_mentions(text) - return '' - - @LoginRequired() - @NotAnonymous() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') @jsonify def delete_comment(self, repo_name, comment_id): co = ChangesetComment.get_or_404(comment_id) if co.repo.repo_name != repo_name: raise HTTPNotFound() - owner = co.author.user_id == c.authuser.user_id + owner = co.author_id == c.authuser.user_id repo_admin = h.HasRepoPermissionAny('repository.admin')(repo_name) if h.HasPermissionAny('hg.admin')() or repo_admin or owner: ChangesetCommentsModel().delete(comment=co) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/compare.py --- a/kallithea/controllers/compare.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/compare.py Sat Dec 24 00:34:38 2016 +0100 @@ -15,7 +15,7 @@ kallithea.controllers.compare ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -compare controller for pylons showing differences between two +compare controller showing differences between two repos, branches, bookmarks or tips This file was forked by the Kallithea project in July 2014. @@ -30,11 +30,12 @@ import logging import re -from webob.exc import HTTPBadRequest -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _ +from webob.exc import HTTPFound, HTTPBadRequest, HTTPNotFound +from kallithea.config.routing import url +from kallithea.lib.utils2 import safe_str, safe_int from kallithea.lib.vcs.utils.hgcompat import unionrepo from kallithea.lib import helpers as h from kallithea.lib.base import BaseRepoController, render @@ -44,7 +45,7 @@ from kallithea.lib.diffs import LimitedDiffContainer from kallithea.controllers.changeset import _ignorews_url, _context_url from kallithea.lib.graphmod import graph_data -from kallithea.lib.compat import json +from kallithea.lib.compat import json, OrderedDict log = logging.getLogger(__name__) @@ -54,13 +55,33 @@ def __before__(self): super(CompareController, self).__before__() + # The base repository has already been retrieved. + c.a_repo = c.db_repo + + # Retrieve the "changeset" repository (default: same as base). + other_repo = request.GET.get('other_repo', None) + if other_repo is None: + c.cs_repo = c.a_repo + else: + c.cs_repo = Repository.get_by_repo_name(other_repo) + if c.cs_repo is None: + msg = _('Could not find other repository %s') % other_repo + h.flash(msg, category='error') + raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name)) + + # Verify that it's even possible to compare these two repositories. + if c.a_repo.scm_instance.alias != c.cs_repo.scm_instance.alias: + msg = _('Cannot compare repositories of different types') + h.flash(msg, category='error') + raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name)) + @staticmethod def _get_changesets(alias, org_repo, org_rev, other_repo, other_rev): """ Returns lists of changesets that can be merged from org_repo@org_rev to other_repo@other_rev ... and the other way - ... and the ancestor that would be used for merge + ... and the ancestors that would be used for merge :param org_repo: repo object, that is most likely the original repo we forked from :param org_rev: the revision we want our compare to be made @@ -68,11 +89,10 @@ all changesets that we need to obtain :param other_rev: revision we want out compare to be made on other_repo """ - ancestor = None + ancestors = None if org_rev == other_rev: org_changesets = [] other_changesets = [] - ancestor = org_rev elif alias == 'hg': #case two independent repos @@ -87,25 +107,20 @@ else: hgrepo = other_repo._repo - if org_repo.EMPTY_CHANGESET in (org_rev, other_rev): - # work around unexpected behaviour in Mercurial < 3.4 - ancestor = org_repo.EMPTY_CHANGESET + ancestors = [hgrepo[ancestor].hex() for ancestor in + hgrepo.revs("id(%s) & ::id(%s)", other_rev, org_rev)] + if ancestors: + log.debug("shortcut found: %s is already an ancestor of %s", other_rev, org_rev) else: - ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev) - if ancestors: - # FIXME: picks arbitrary ancestor - but there is usually only one - try: - ancestor = hgrepo[ancestors.first()].hex() - except AttributeError: - # removed in hg 3.2 - ancestor = hgrepo[ancestors[0]].hex() + log.debug("no shortcut found: %s is not an ancestor of %s", other_rev, org_rev) + ancestors = [hgrepo[ancestor].hex() for ancestor in + hgrepo.revs("heads(::id(%s) & ::id(%s))", org_rev, other_rev)] # FIXME: expensive! other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)", other_rev, org_rev, org_rev) other_changesets = [other_repo.get_changeset(rev) for rev in other_revs] org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)", org_rev, other_rev, other_rev) - org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs] elif alias == 'git': @@ -114,10 +129,10 @@ from dulwich.client import SubprocessGitClient gitrepo = Repo(org_repo.path) - SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo) + SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo) gitrepo_remote = Repo(other_repo.path) - SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote) + SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote) revs = [] for x in gitrepo_remote.get_walker(include=[other_rev], @@ -126,10 +141,14 @@ other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)] if other_changesets: - ancestor = other_changesets[0].parents[0].raw_id + ancestors = [other_changesets[0].parents[0].raw_id] else: # no changesets from other repo, ancestor is the other_rev - ancestor = other_rev + ancestors = [other_rev] + + # dulwich 0.9.9 doesn't have a Repo.close() so we have to mess with internals: + gitrepo.object_store.close() + gitrepo_remote.object_store.close() else: so, se = org_repo.run_git_command( @@ -141,23 +160,19 @@ so, se = org_repo.run_git_command( ['merge-base', org_rev, other_rev] ) - ancestor = re.findall(r'[0-9a-fA-F]{40}', so)[0] + ancestors = [re.findall(r'[0-9a-fA-F]{40}', so)[0]] org_changesets = [] else: raise Exception('Bad alias only git and hg is allowed') - return other_changesets, org_changesets, ancestor + return other_changesets, org_changesets, ancestors @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') def index(self, repo_name): c.compare_home = True - org_repo = c.db_repo.repo_name - other_repo = request.GET.get('other_repo', org_repo) - c.a_repo = Repository.get_by_repo_name(org_repo) - c.cs_repo = Repository.get_by_repo_name(other_repo) c.a_ref_name = c.cs_ref_name = _('Select changeset') return render('compare/compare_diff.html') @@ -168,8 +183,6 @@ org_ref_name = org_ref_name.strip() other_ref_name = other_ref_name.strip() - org_repo = c.db_repo.repo_name - other_repo = request.GET.get('other_repo', org_repo) # If merge is True: # Show what org would get if merged with other: # List changesets that are ancestors of other but not of org. @@ -187,9 +200,9 @@ c.as_form = partial and request.GET.get('as_form') # swap url for compare_diff page - never partial and never as_form c.swap_url = h.url('compare_url', - repo_name=other_repo, + repo_name=c.cs_repo.repo_name, org_ref_type=other_ref_type, org_ref_name=other_ref_name, - other_repo=org_repo, + other_repo=c.a_repo.repo_name, other_ref_type=org_ref_type, other_ref_name=org_ref_name, merge=merge or '') @@ -197,64 +210,56 @@ c.ignorews_url = _ignorews_url c.context_url = _context_url ignore_whitespace = request.GET.get('ignorews') == '1' - line_context = request.GET.get('context', 3) - - org_repo = Repository.get_by_repo_name(org_repo) - other_repo = Repository.get_by_repo_name(other_repo) - - if org_repo is None: - msg = 'Could not find org repo %s' % org_repo - log.error(msg) - h.flash(msg, category='error') - return redirect(url('compare_home', repo_name=c.repo_name)) + line_context = safe_int(request.GET.get('context'), 3) - if other_repo is None: - msg = 'Could not find other repo %s' % other_repo - log.error(msg) - h.flash(msg, category='error') - return redirect(url('compare_home', repo_name=c.repo_name)) - - if org_repo.scm_instance.alias != other_repo.scm_instance.alias: - msg = 'compare of two different kind of remote repos not available' - log.error(msg) - h.flash(msg, category='error') - return redirect(url('compare_home', repo_name=c.repo_name)) - - c.a_rev = self._get_ref_rev(org_repo, org_ref_type, org_ref_name, + c.a_rev = self._get_ref_rev(c.a_repo, org_ref_type, org_ref_name, returnempty=True) - c.cs_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name) + c.cs_rev = self._get_ref_rev(c.cs_repo, other_ref_type, other_ref_name) c.compare_home = False - c.a_repo = org_repo c.a_ref_name = org_ref_name c.a_ref_type = org_ref_type - c.cs_repo = other_repo c.cs_ref_name = other_ref_name c.cs_ref_type = other_ref_type - c.cs_ranges, c.cs_ranges_org, c.ancestor = self._get_changesets( - org_repo.scm_instance.alias, org_repo.scm_instance, c.a_rev, - other_repo.scm_instance, c.cs_rev) + c.cs_ranges, c.cs_ranges_org, c.ancestors = self._get_changesets( + c.a_repo.scm_instance.alias, c.a_repo.scm_instance, c.a_rev, + c.cs_repo.scm_instance, c.cs_rev) raw_ids = [x.raw_id for x in c.cs_ranges] - c.cs_comments = other_repo.get_comments(raw_ids) - c.statuses = other_repo.statuses(raw_ids) + c.cs_comments = c.cs_repo.get_comments(raw_ids) + c.statuses = c.cs_repo.statuses(raw_ids) revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = json.dumps(graph_data(c.cs_repo.scm_instance, revs)) if partial: return render('compare/compare_cs.html') - if merge and c.ancestor: + + org_repo = c.a_repo + other_repo = c.cs_repo + + if merge: + rev1 = msg = None + if not c.cs_ranges: + msg = _('Cannot show empty diff') + elif not c.ancestors: + msg = _('No ancestor found for merge diff') + elif len(c.ancestors) == 1: + rev1 = c.ancestors[0] + else: + msg = _('Multiple merge ancestors found for merge compare') + if rev1 is None: + h.flash(msg, category='error') + log.error(msg) + raise HTTPNotFound + # case we want a simple diff without incoming changesets, # previewing what will be merged. # Make the diff on the other repo (which is known to have other_rev) log.debug('Using ancestor %s as rev1 instead of %s', - c.ancestor, c.a_rev) - rev1 = c.ancestor + rev1, c.a_rev) org_repo = other_repo else: # comparing tips, not necessarily linearly related - if merge: - log.error('Unable to find ancestor revision') if org_repo != other_repo: # TODO: we could do this by using hg unionrepo log.error('cannot compare across repos %s and %s', org_repo, other_repo) @@ -278,18 +283,17 @@ if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True - c.files = [] - c.changes = {} + c.file_diff_data = [] c.lines_added = 0 c.lines_deleted = 0 for f in _parsed: st = f['stats'] - if not st['binary']: - c.lines_added += st['added'] - c.lines_deleted += st['deleted'] - fid = h.FID('', f['filename']) - c.files.append([fid, f['operation'], f['filename'], f['stats']]) - htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f]) - c.changes[fid] = [f['operation'], f['filename'], htmldiff] + c.lines_added += st['added'] + c.lines_deleted += st['deleted'] + filename = f['filename'] + fid = h.FID('', filename) + diff = diff_processor.as_html(enable_comments=False, + parsed_lines=[f]) + c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st)) return render('compare/compare_diff.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/error.py --- a/kallithea/controllers/error.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/error.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,7 +28,6 @@ import os import cgi import logging -import paste.fileapp from pylons import tmpl_context as c, request, config from pylons.i18n.translation import _ @@ -73,21 +72,6 @@ return render('/errors/error_document.html') - def img(self, id): - """Serve Pylons' stock images""" - return self._serve_file(os.path.join(media_path, 'img', id)) - - def style(self, id): - """Serve Pylons' stock stylesheets""" - return self._serve_file(os.path.join(media_path, 'style', id)) - - def _serve_file(self, path): - """Call Paste's FileApp (a WSGI application) to serve the file - at the specified path - """ - fapp = paste.fileapp.FileApp(path) - return fapp(request.environ, self.start_response) - def get_error_explanation(self, code): """ get the error explanations of int codes [400, 401, 403, 404, 500]""" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/feed.py --- a/kallithea/controllers/feed.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/feed.py Sat Dec 24 00:34:38 2016 +0100 @@ -117,7 +117,7 @@ def atom(self, repo_name): """Produce an atom-1.0 feed via feedgenerator module""" - @cache_region('long_term') + @cache_region('long_term', '_get_feed_from_cache') def _get_feed_from_cache(key, kind): feed = Atom1Feed( title=self.title % repo_name, @@ -142,13 +142,13 @@ kind = 'ATOM' valid = CacheInvalidation.test_and_set_valid(repo_name, kind) if not valid: - region_invalidate(_get_feed_from_cache, None, repo_name, kind) + region_invalidate(_get_feed_from_cache, None, '_get_feed_from_cache', repo_name, kind) return _get_feed_from_cache(repo_name, kind) def rss(self, repo_name): """Produce an rss2 feed via feedgenerator module""" - @cache_region('long_term') + @cache_region('long_term', '_get_feed_from_cache') def _get_feed_from_cache(key, kind): feed = Rss201rev2Feed( title=self.title % repo_name, @@ -173,5 +173,5 @@ kind = 'RSS' valid = CacheInvalidation.test_and_set_valid(repo_name, kind) if not valid: - region_invalidate(_get_feed_from_cache, None, repo_name, kind) + region_invalidate(_get_feed_from_cache, None, '_get_feed_from_cache', repo_name, kind) return _get_feed_from_cache(repo_name, kind) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/files.py --- a/kallithea/controllers/files.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/files.py Sat Dec 24 00:34:38 2016 +0100 @@ -26,29 +26,31 @@ """ import os +import posixpath import logging import traceback import tempfile import shutil -from pylons import request, response, tmpl_context as c, url +from pylons import request, response, tmpl_context as c from pylons.i18n.translation import _ -from pylons.controllers.util import redirect +from webob.exc import HTTPFound + +from kallithea.config.routing import url from kallithea.lib.utils import jsonify, action_logger - from kallithea.lib import diffs from kallithea.lib import helpers as h from kallithea.lib.compat import OrderedDict -from kallithea.lib.utils2 import convert_line_endings, detect_mode, safe_str,\ - str2bool +from kallithea.lib.utils2 import convert_line_endings, detect_mode, safe_str, \ + str2bool, safe_int from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from kallithea.lib.base import BaseRepoController, render from kallithea.lib.vcs.backends.base import EmptyChangeset from kallithea.lib.vcs.conf import settings from kallithea.lib.vcs.exceptions import RepositoryError, \ ChangesetDoesNotExistError, EmptyRepositoryError, \ - ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\ + ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError, \ NodeDoesNotExistError, ChangesetError, NodeError from kallithea.lib.vcs.nodes import FileNode @@ -56,7 +58,7 @@ from kallithea.model.scm import ScmModel from kallithea.model.db import Repository -from kallithea.controllers.changeset import anchor_url, _ignorews_url,\ +from kallithea.controllers.changeset import anchor_url, _ignorews_url, \ _context_url, get_line_ctx, get_ignore_ws from webob.exc import HTTPNotFound from kallithea.lib.exceptions import NonRelativePathError @@ -92,7 +94,7 @@ h.flash(h.literal(_('There are no files yet. %s') % add_new), category='warning') raise HTTPNotFound() - except(ChangesetDoesNotExistError, LookupError), e: + except (ChangesetDoesNotExistError, LookupError): msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() @@ -112,7 +114,7 @@ file_node = cs.get_node(path) if file_node.is_dir(): raise RepositoryError('given path is a directory') - except(ChangesetDoesNotExistError,), e: + except ChangesetDoesNotExistError: msg = _('Such revision does not exist for this repository') h.flash(msg, category='error') raise HTTPNotFound() @@ -137,6 +139,7 @@ c.f_path = f_path c.annotate = annotate cur_rev = c.changeset.revision + c.fulldiff = request.GET.get('fulldiff') # prev link try: @@ -164,10 +167,6 @@ if c.file.is_file(): c.load_full_history = False - file_last_cs = c.file.last_changeset - c.file_changeset = (c.changeset - if c.changeset.revision < file_last_cs.revision - else file_last_cs) #determine if we're on branch head _branches = c.db_repo_scm_instance.branches c.on_branch_head = revision in _branches.keys() + _branches.values() @@ -306,7 +305,7 @@ % (h.person_by_id(repo.locked[0]), h.fmt_date(h.time_to_datetime(repo.locked[1]))), 'warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip')) # check if revision is a branch identifier- basically we cannot @@ -316,7 +315,7 @@ if revision not in _branches.keys() + _branches.values(): h.flash(_('You can only delete files with revision ' 'being a valid branch'), category='warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip', f_path=f_path)) @@ -352,7 +351,7 @@ except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') - return redirect(url('changeset_home', + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_delete.html') @@ -366,7 +365,7 @@ % (h.person_by_id(repo.locked[0]), h.fmt_date(h.time_to_datetime(repo.locked[1]))), 'warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip')) # check if revision is a branch identifier- basically we cannot @@ -376,7 +375,7 @@ if revision not in _branches.keys() + _branches.values(): h.flash(_('You can only edit files with revision ' 'being a valid branch'), category='warning') - return redirect(h.url('files_home', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip', f_path=f_path)) @@ -386,7 +385,7 @@ c.file = self.__get_filenode(c.cs, f_path) if c.file.is_binary: - return redirect(url('files_home', repo_name=c.repo_name, + raise HTTPFound(location=url('files_home', repo_name=c.repo_name, revision=c.cs.raw_id, f_path=f_path)) c.default_message = _('Edited file %s via Kallithea') % (f_path) c.f_path = f_path @@ -405,7 +404,7 @@ if content == old_content: h.flash(_('No changes'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) try: self.scm_model.commit_change(repo=c.db_repo_scm_instance, @@ -418,7 +417,7 @@ except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') - return redirect(url('changeset_home', + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_edit.html') @@ -427,13 +426,13 @@ @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin') def add(self, repo_name, revision, f_path): - repo = Repository.get_by_repo_name(repo_name) + repo = c.db_repo if repo.enable_locking and repo.locked[0]: h.flash(_('This repository 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', + raise HTTPFound(location=h.url('files_home', repo_name=repo_name, revision='tip')) r_post = request.POST @@ -462,15 +461,15 @@ if not content: h.flash(_('No content'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) if not filename: h.flash(_('No filename'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) #strip all crap out of file, just leave the basename filename = os.path.basename(filename) - node_path = os.path.join(location, filename) + node_path = posixpath.join(location, filename) author = self.authuser.full_contact try: @@ -492,14 +491,14 @@ except NonRelativePathError as e: h.flash(_('Location must be relative path and must not ' 'contain .. in path'), category='warning') - return redirect(url('changeset_home', repo_name=c.repo_name, + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) except (NodeError, NodeAlreadyExistsError) as e: h.flash(_(e), category='error') except Exception: log.error(traceback.format_exc()) h.flash(_('Error occurred during commit'), category='error') - return redirect(url('changeset_home', + raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name, revision='tip')) return render('files/files_add.html') @@ -548,7 +547,7 @@ archive_path = None cached_archive_path = None archive_cache_dir = CONFIG.get('archive_cache_dir') - if archive_cache_dir and not subrepos: # TOOD: subrepo caching? + if archive_cache_dir and not subrepos: # TODO: subrepo caching? if not os.path.isdir(archive_cache_dir): os.makedirs(archive_cache_dir) cached_archive_path = os.path.join(archive_cache_dir, archive_name) @@ -596,7 +595,7 @@ 'repository.admin') def diff(self, repo_name, f_path): ignore_whitespace = request.GET.get('ignorews') == '1' - line_context = request.GET.get('context', 3) + line_context = safe_int(request.GET.get('context'), 3) diff2 = request.GET.get('diff2', '') diff1 = request.GET.get('diff1', '') or diff2 c.action = request.GET.get('diff') @@ -620,7 +619,7 @@ _url = url('files_home', repo_name=c.repo_name, revision=diff1, f_path=c.f_path) - return redirect(_url) + raise HTTPFound(location=_url) try: if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]: c.changeset_1 = c.db_repo_scm_instance.get_changeset(diff1) @@ -655,7 +654,7 @@ node2 = FileNode(f_path, '', changeset=c.changeset_2) except (RepositoryError, NodeError): log.error(traceback.format_exc()) - return redirect(url('files_home', repo_name=c.repo_name, + raise HTTPFound(location=url('files_home', repo_name=c.repo_name, f_path=f_path)) if c.action == 'download': @@ -685,20 +684,15 @@ ign_whitespace_lcl = get_ignore_ws(fid, request.GET) lim = request.GET.get('fulldiff') or self.cut_off_limit - _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1, + c.a_rev, c.cs_rev, a_path, diff, st, op = diffs.wrapped_diff(filenode_old=node1, filenode_new=node2, cut_off_limit=lim, ignore_whitespace=ign_whitespace_lcl, line_context=line_context_lcl, enable_comments=False) - op = '' - filename = node1.path - cs_changes = { - 'fid': [cs1, cs2, op, filename, diff, st] - } - c.changes = cs_changes + c.file_diff_data = [(fid, fid, op, a_path, node2.path, diff, st)] - return render('files/file_diff.html') + return render('files/file_diff.html') @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/followers.py --- a/kallithea/controllers/followers.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/followers.py Sat Dec 24 00:34:38 2016 +0100 @@ -29,11 +29,11 @@ from pylons import tmpl_context as c, request -from kallithea.lib.helpers import Page from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from kallithea.lib.base import BaseRepoController, render +from kallithea.lib.page import Page +from kallithea.lib.utils2 import safe_int from kallithea.model.db import UserFollowing -from kallithea.lib.utils2 import safe_int log = logging.getLogger(__name__) @@ -47,9 +47,9 @@ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') def followers(self, repo_name): - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) repo_id = c.db_repo.repo_id - d = UserFollowing.get_repo_followers(repo_id)\ + d = UserFollowing.get_repo_followers(repo_id) \ .order_by(UserFollowing.follows_from) c.followers_pager = Page(d, page=p, items_per_page=20) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/forks.py --- a/kallithea/controllers/forks.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/forks.py Sat Dec 24 00:34:38 2016 +0100 @@ -30,21 +30,22 @@ import traceback from formencode import htmlfill -from pylons import tmpl_context as c, request, url -from pylons.controllers.util import redirect +from pylons import tmpl_context as c, request from pylons.i18n.translation import _ +from webob.exc import HTTPFound import kallithea.lib.helpers as h -from kallithea.lib.helpers import Page +from kallithea.config.routing import url from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ NotAnonymous, HasRepoPermissionAny, HasPermissionAnyDecorator, HasPermissionAny from kallithea.lib.base import BaseRepoController, render +from kallithea.lib.page import Page +from kallithea.lib.utils2 import safe_int from kallithea.model.db import Repository, UserFollowing, User, Ui from kallithea.model.repo import RepoModel from kallithea.model.forms import RepoForkForm from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices -from kallithea.lib.utils2 import safe_int log = logging.getLogger(__name__) @@ -62,26 +63,24 @@ c.landing_revs_choices, c.landing_revs = ScmModel().get_repo_landing_revs() - c.can_update = Ui.get_by_key(Ui.HOOK_UPDATE).ui_active + c.can_update = Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active - def __load_data(self, repo_name=None): + def __load_data(self): """ Load defaults settings for edit, and update - - :param repo_name: """ self.__load_defaults() - c.repo_info = db_repo = Repository.get_by_repo_name(repo_name) - repo = db_repo.scm_instance + c.repo_info = c.db_repo + repo = c.db_repo.scm_instance if c.repo_info is None: - h.not_mapped_error(repo_name) - return redirect(url('repos')) + h.not_mapped_error(c.repo_name) + raise HTTPFound(location=url('repos')) c.default_user_id = User.get_default_user().user_id - c.in_public_journal = UserFollowing.query()\ - .filter(UserFollowing.user_id == c.default_user_id)\ + c.in_public_journal = UserFollowing.query() \ + .filter(UserFollowing.user_id == c.default_user_id) \ .filter(UserFollowing.follows_repository == c.repo_info).scalar() if c.repo_info.stats: @@ -98,7 +97,7 @@ c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100) - defaults = RepoModel()._get_defaults(repo_name) + defaults = RepoModel()._get_defaults(c.repo_name) # alter the description to indicate a fork defaults['description'] = ('fork of repository: %s \n%s' % (defaults['repo_name'], @@ -112,7 +111,7 @@ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') def forks(self, repo_name): - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) repo_id = c.db_repo.repo_id d = [] for r in Repository.get_repo_forks(repo_id): @@ -137,9 +136,9 @@ c.repo_info = Repository.get_by_repo_name(repo_name) if not c.repo_info: h.not_mapped_error(repo_name) - return redirect(url('home')) + raise HTTPFound(location=url('home')) - defaults = self.__load_data(repo_name) + defaults = self.__load_data() return htmlfill.render( render('forks/fork.html'), @@ -164,15 +163,13 @@ form_result = _form.to_python(dict(request.POST)) # an approximation that is better than nothing - if not Ui.get_by_key(Ui.HOOK_UPDATE).ui_active: + if not Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active: form_result['update_after_clone'] = False # create fork is done sometimes async on celery, db transaction # management is handled there. task = RepoModel().create_fork(form_result, self.authuser.user_id) - from celery.result import BaseAsyncResult - if isinstance(task, BaseAsyncResult): - task_id = task.task_id + task_id = task.task_id except formencode.Invalid as errors: return htmlfill.render( render('forks/fork.html'), @@ -186,6 +183,6 @@ h.flash(_('An error occurred during repository forking %s') % repo_name, category='error') - return redirect(h.url('repo_creating_home', + raise HTTPFound(location=h.url('repo_creating_home', repo_name=form_result['repo_name_full'], task_id=task_id)) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/home.py --- a/kallithea/controllers/home.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/home.py Sat Dec 24 00:34:38 2016 +0100 @@ -57,13 +57,10 @@ c.groups = self.scm_model.get_repo_groups() c.group = None - c.repos_list = Repository.query()\ - .filter(Repository.group_id == None)\ - .order_by(func.lower(Repository.repo_name))\ - .all() + repos_list = Repository.query(sorted=True).filter_by(group=None).all() - repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list, - admin=False) + repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, + admin=False, short_name=True) #json used to render the grid c.data = json.dumps(repos_data) @@ -75,26 +72,35 @@ #wrapper for conditional cache def _c(): log.debug('generating switcher repo/groups list') - all_repos = Repository.query().order_by(Repository.repo_name).all() - repo_iter = self.scm_model.get_repos(all_repos, simple=True) - all_groups = RepoGroup.query().order_by(RepoGroup.group_name).all() + all_repos = Repository.query(sorted=True).all() + repo_iter = self.scm_model.get_repos(all_repos) + all_groups = RepoGroup.query(sorted=True).all() repo_groups_iter = self.scm_model.get_repo_groups(all_groups) res = [{ 'text': _('Groups'), 'children': [ - {'id': obj.group_name, 'text': obj.group_name, - 'type': 'group', 'obj': {}} for obj in repo_groups_iter] - }, { + {'id': obj.group_name, + 'text': obj.group_name, + 'type': 'group', + 'obj': {}} + for obj in repo_groups_iter + ], + }, + { 'text': _('Repositories'), 'children': [ - {'id': obj['name'], 'text': obj['name'], - 'type': 'repo', 'obj': obj['dbrepo']} for obj in repo_iter] + {'id': obj.repo_name, + 'text': obj.repo_name, + 'type': 'repo', + 'obj': obj.get_dict()} + for obj in repo_iter + ], }] data = { 'more': False, - 'results': res + 'results': res, } return data @@ -109,17 +115,6 @@ @LoginRequired() @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') - def branch_tag_switcher(self, repo_name): - if request.is_xhr: - c.db_repo = Repository.get_by_repo_name(repo_name) - if c.db_repo: - c.db_repo_scm_instance = c.db_repo.scm_instance - return render('/switch_to_list.html') - raise HTTPBadRequest() - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') @jsonify def repo_refs_data(self, repo_name): repo = Repository.get_by_repo_name(repo_name).scm_instance @@ -130,6 +125,12 @@ 'text': _('Branch'), 'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches] }) + _closed_branches = repo.closed_branches.items() + if _closed_branches: + res.append({ + 'text': _('Closed Branches'), + 'children': [{'id': rev, 'text': name, 'type': 'closed-branch'} for name, rev in _closed_branches] + }) _tags = repo.tags.items() if _tags: res.append({ diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/journal.py --- a/kallithea/controllers/journal.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/journal.py Sat Dec 24 00:34:38 2016 +0100 @@ -15,7 +15,7 @@ kallithea.controllers.journal ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Journal controller for pylons +Journal controller This file was forked by the Kallithea project in July 2014. Original author and date, and relevant copyright and licensing information is below: @@ -37,19 +37,20 @@ from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed from webob.exc import HTTPBadRequest -from pylons import request, tmpl_context as c, response, url +from pylons import request, tmpl_context as c, response from pylons.i18n.translation import _ +from kallithea.config.routing import url from kallithea.controllers.admin.admin import _journal_filter from kallithea.model.db import UserLog, UserFollowing, Repository, User from kallithea.model.meta import Session from kallithea.model.repo import RepoModel import kallithea.lib.helpers as h -from kallithea.lib.helpers import Page from kallithea.lib.auth import LoginRequired, NotAnonymous from kallithea.lib.base import BaseController, render +from kallithea.lib.compat import json +from kallithea.lib.page import Page from kallithea.lib.utils2 import safe_int, AttributeDict -from kallithea.lib.compat import json log = logging.getLogger(__name__) @@ -77,10 +78,10 @@ return groups def _get_journal_data(self, following_repos): - repo_ids = [x.follows_repository.repo_id for x in following_repos - if x.follows_repository is not None] - user_ids = [x.follows_user.user_id for x in following_repos - if x.follows_user is not None] + repo_ids = [x.follows_repository_id for x in following_repos + if x.follows_repository_id is not None] + user_ids = [x.follows_user_id for x in following_repos + if x.follows_user_id is not None] filtering_criterion = None @@ -92,12 +93,12 @@ if not repo_ids and user_ids: filtering_criterion = UserLog.user_id.in_(user_ids) if filtering_criterion is not None: - journal = self.sa.query(UserLog)\ - .options(joinedload(UserLog.user))\ + journal = self.sa.query(UserLog) \ + .options(joinedload(UserLog.user)) \ .options(joinedload(UserLog.repository)) #filter journal = _journal_filter(journal, c.search_term) - journal = journal.filter(filtering_criterion)\ + journal = journal.filter(filtering_criterion) \ .order_by(UserLog.action_date.desc()) else: journal = [] @@ -192,11 +193,11 @@ @NotAnonymous() def index(self): # Return a rendered template - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) c.user = User.get(self.authuser.user_id) - c.following = self.sa.query(UserFollowing)\ - .filter(UserFollowing.user_id == self.authuser.user_id)\ - .options(joinedload(UserFollowing.follows_repository))\ + c.following = self.sa.query(UserFollowing) \ + .filter(UserFollowing.user_id == self.authuser.user_id) \ + .options(joinedload(UserFollowing.follows_repository)) \ .all() journal = self._get_journal_data(c.following) @@ -205,15 +206,13 @@ return url.current(filter=c.search_term, **kw) c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator) - c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) + c.journal_day_aggregate = self._get_daily_aggregate(c.journal_pager) if request.environ.get('HTTP_X_PARTIAL_XHR'): return render('journal/journal_data.html') - repos_list = Session().query(Repository)\ - .filter(Repository.user_id == - self.authuser.user_id)\ - .order_by(func.lower(Repository.repo_name)).all() + repos_list = Repository.query(sorted=True) \ + .filter_by(owner_id=self.authuser.user_id).all() repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, admin=True) @@ -255,7 +254,7 @@ cs_cache = repo.changeset_cache row = { "menu": quick_menu(repo.repo_name), - "raw_name": repo.repo_name.lower(), + "raw_name": repo.repo_name, "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state, repo.private, repo.fork), "last_changeset": last_rev(repo.repo_name, cs_cache), @@ -280,9 +279,9 @@ """ Produce an atom-1.0 feed via feedgenerator module """ - following = self.sa.query(UserFollowing)\ - .filter(UserFollowing.user_id == self.authuser.user_id)\ - .options(joinedload(UserFollowing.follows_repository))\ + following = self.sa.query(UserFollowing) \ + .filter(UserFollowing.user_id == self.authuser.user_id) \ + .options(joinedload(UserFollowing.follows_repository)) \ .all() return self._atom_feed(following, public=False) @@ -292,9 +291,9 @@ """ Produce an rss feed via feedgenerator module """ - following = self.sa.query(UserFollowing)\ - .filter(UserFollowing.user_id == self.authuser.user_id)\ - .options(joinedload(UserFollowing.follows_repository))\ + following = self.sa.query(UserFollowing) \ + .filter(UserFollowing.user_id == self.authuser.user_id) \ + .options(joinedload(UserFollowing.follows_repository)) \ .all() return self._rss_feed(following, public=False) @@ -312,7 +311,7 @@ log.error(traceback.format_exc()) raise HTTPBadRequest() - repo_id = request.POST.get('follows_repo_id') + repo_id = request.POST.get('follows_repository_id') if repo_id: try: self.scm_model.toggle_following_repo(repo_id, @@ -328,18 +327,18 @@ @LoginRequired() def public_journal(self): # Return a rendered template - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) - c.following = self.sa.query(UserFollowing)\ - .filter(UserFollowing.user_id == self.authuser.user_id)\ - .options(joinedload(UserFollowing.follows_repository))\ + c.following = self.sa.query(UserFollowing) \ + .filter(UserFollowing.user_id == self.authuser.user_id) \ + .options(joinedload(UserFollowing.follows_repository)) \ .all() journal = self._get_journal_data(c.following) c.journal_pager = Page(journal, page=p, items_per_page=20) - c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager) + c.journal_day_aggregate = self._get_daily_aggregate(c.journal_pager) if request.environ.get('HTTP_X_PARTIAL_XHR'): return render('journal/journal_data.html') @@ -351,9 +350,9 @@ """ Produce an atom-1.0 feed via feedgenerator module """ - c.following = self.sa.query(UserFollowing)\ - .filter(UserFollowing.user_id == self.authuser.user_id)\ - .options(joinedload(UserFollowing.follows_repository))\ + c.following = self.sa.query(UserFollowing) \ + .filter(UserFollowing.user_id == self.authuser.user_id) \ + .options(joinedload(UserFollowing.follows_repository)) \ .all() return self._atom_feed(c.following) @@ -363,9 +362,9 @@ """ Produce an rss2 feed via feedgenerator module """ - c.following = self.sa.query(UserFollowing)\ - .filter(UserFollowing.user_id == self.authuser.user_id)\ - .options(joinedload(UserFollowing.follows_repository))\ + c.following = self.sa.query(UserFollowing) \ + .filter(UserFollowing.user_id == self.authuser.user_id) \ + .options(joinedload(UserFollowing.follows_repository)) \ .all() return self._rss_feed(c.following) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/login.py --- a/kallithea/controllers/login.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/login.py Sat Dec 24 00:34:38 2016 +0100 @@ -31,12 +31,12 @@ import formencode from formencode import htmlfill +from pylons.i18n.translation import _ +from pylons import request, session, tmpl_context as c from webob.exc import HTTPFound, HTTPBadRequest -from pylons.i18n.translation import _ -from pylons.controllers.util import redirect -from pylons import request, session, tmpl_context as c, url import kallithea.lib.helpers as h +from kallithea.config.routing import url from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator from kallithea.lib.base import BaseController, log_in_user, render from kallithea.lib.exceptions import UserCreationError @@ -79,25 +79,24 @@ else: c.came_from = url('home') - not_default = self.authuser.username != User.DEFAULT_USER ip_allowed = AuthUser.check_ip_allowed(self.authuser, self.ip_addr) # redirect if already logged in - if self.authuser.is_authenticated and not_default and ip_allowed: + if self.authuser.is_authenticated and ip_allowed: raise HTTPFound(location=c.came_from) if request.POST: # import Login Form validator class - login_form = LoginForm() + login_form = LoginForm()() try: c.form_result = login_form.to_python(dict(request.POST)) # form checks for username/password, now we're authenticated username = c.form_result['username'] - user = User.get_by_username(username, case_insensitive=True) + user = User.get_by_username_or_email(username, case_insensitive=True) except formencode.Invalid as errors: defaults = errors.value # remove password from filling in form again - del defaults['password'] + defaults.pop('password', None) return htmlfill.render( render('/login.html'), defaults=errors.value, @@ -121,7 +120,7 @@ @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate') def register(self): - c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\ + c.auto_active = 'hg.register.auto_activate' in User.get_default_user() \ .AuthUser.permissions['global'] settings = Setting.get_app_settings() @@ -149,10 +148,10 @@ error_dict=error_dict) UserModel().create_registration(form_result) - h.flash(_('You have successfully registered into Kallithea'), + h.flash(_('You have successfully registered with %s') % (c.site_name or 'Kallithea'), category='success') Session().commit() - return redirect(url('login_home')) + raise HTTPFound(location=url('login_home')) except formencode.Invalid as errors: return htmlfill.render( @@ -196,7 +195,7 @@ redirect_link = UserModel().send_reset_password_email(form_result) h.flash(_('A password reset confirmation code has been sent'), category='success') - return redirect(redirect_link) + raise HTTPFound(location=redirect_link) except formencode.Invalid as errors: return htmlfill.render( @@ -249,12 +248,12 @@ UserModel().reset_password(form_result['email'], form_result['password']) h.flash(_('Successfully updated password'), category='success') - return redirect(url('login_home')) + raise HTTPFound(location=url('login_home')) def logout(self): session.delete() log.info('Logging out and deleting session for user') - redirect(url('home')) + raise HTTPFound(location=url('home')) def authentication_token(self): """Return the CSRF protection token for the session - just like it diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/pullrequests.py --- a/kallithea/controllers/pullrequests.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/pullrequests.py Sat Dec 24 00:34:38 2016 +0100 @@ -30,27 +30,26 @@ import formencode import re -from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest - -from pylons import request, tmpl_context as c, url -from pylons.controllers.util import redirect +from pylons import request, tmpl_context as c from pylons.i18n.translation import _ +from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest -from kallithea.lib.vcs.utils.hgcompat import unionrepo -from kallithea.lib.compat import json -from kallithea.lib.base import BaseRepoController, render -from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\ - NotAnonymous -from kallithea.lib.helpers import Page +from kallithea.config.routing import url from kallithea.lib import helpers as h from kallithea.lib import diffs +from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ + NotAnonymous +from kallithea.lib.base import BaseRepoController, render +from kallithea.lib.compat import json, OrderedDict +from kallithea.lib.diffs import LimitedDiffContainer from kallithea.lib.exceptions import UserInvalidException +from kallithea.lib.page import Page from kallithea.lib.utils import action_logger, jsonify +from kallithea.lib.vcs.exceptions import EmptyRepositoryError, ChangesetDoesNotExistError from kallithea.lib.vcs.utils import safe_str -from kallithea.lib.vcs.exceptions import EmptyRepositoryError -from kallithea.lib.diffs import LimitedDiffContainer -from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment,\ - PullRequestReviewers, User +from kallithea.lib.vcs.utils.hgcompat import unionrepo +from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment, \ + PullRequestReviewer, User from kallithea.model.pull_request import PullRequestModel from kallithea.model.meta import Session from kallithea.model.repo import RepoModel @@ -58,7 +57,8 @@ from kallithea.model.changeset_status import ChangesetStatusModel from kallithea.model.forms import PullRequestForm, PullRequestPostForm from kallithea.lib.utils2 import safe_int -from kallithea.controllers.changeset import _ignorews_url, _context_url +from kallithea.controllers.changeset import _ignorews_url, _context_url, \ + create_comment from kallithea.controllers.compare import CompareController from kallithea.lib.graphmod import graph_data @@ -140,8 +140,7 @@ continue n = 'tag:%s:%s' % (tag, tagrev) tags.append((n, tag)) - if rev == tagrev: - selected = n + # note: even if rev == tagrev, don't select the static tag - it must be chosen explicitly # prio 1: rev was selected as existing entry above @@ -182,9 +181,12 @@ if pull_request.is_closed(): return False - owner = self.authuser.user_id == pull_request.user_id - reviewer = self.authuser.user_id in [x.user_id for x in - pull_request.reviewers] + owner = self.authuser.user_id == pull_request.owner_id + reviewer = PullRequestReviewer.query() \ + .filter(PullRequestReviewer.pull_request == pull_request) \ + .filter(PullRequestReviewer.user_id == self.authuser.user_id) \ + .count() != 0 + return self.authuser.admin or owner or reviewer @LoginRequired() @@ -193,9 +195,14 @@ def show_all(self, repo_name): c.from_ = request.GET.get('from_') or '' c.closed = request.GET.get('closed') or '' - c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed) - c.repo_name = repo_name - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) + + q = PullRequest.query(include_closed=c.closed, sorted=True) + if c.from_: + q = q.filter_by(org_repo=c.db_repo) + else: + q = q.filter_by(other_repo=c.db_repo) + c.pull_requests = q.all() c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100) @@ -206,22 +213,24 @@ def show_my(self): c.closed = request.GET.get('closed') or '' - def _filter(pr): - s = sorted(pr, key=lambda o: o.created_on, reverse=True) - if not c.closed: - s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s) - return s + c.my_pull_requests = PullRequest.query( + include_closed=c.closed, + sorted=True, + ).filter_by(owner_id=self.authuser.user_id).all() - c.my_pull_requests = _filter(PullRequest.query()\ - .filter(PullRequest.user_id == - self.authuser.user_id)\ - .all()) - - c.participate_in_pull_requests = _filter(PullRequest.query()\ - .join(PullRequestReviewers)\ - .filter(PullRequestReviewers.user_id == - self.authuser.user_id)\ - ) + c.participate_in_pull_requests = [] + c.participate_in_pull_requests_todo = [] + done_status = set([ChangesetStatus.STATUS_APPROVED, ChangesetStatus.STATUS_REJECTED]) + for pr in PullRequest.query( + include_closed=c.closed, + reviewer_id=self.authuser.user_id, + sorted=True, + ): + status = pr.user_review_status(c.authuser.user_id) # very inefficient!!! + if status in done_status: + c.participate_in_pull_requests.append(pr) + else: + c.participate_in_pull_requests_todo.append(pr) return render('/pullrequests/pullrequest_show_my.html') @@ -237,7 +246,7 @@ except EmptyRepositoryError as e: h.flash(h.literal(_('There are no changesets yet')), category='warning') - redirect(url('summary_home', repo_name=org_repo.repo_name)) + raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name)) org_rev = request.GET.get('rev_end') # rev_start is not directly useful - its parent could however be used @@ -288,7 +297,7 @@ 'repository.admin') @jsonify def repo_info(self, repo_name): - repo = RepoModel()._get_repo(repo_name) + repo = c.db_repo refs, selected_ref = self._get_repo_refs(repo.scm_instance) return { 'description': repo.description.split('\n', 1)[0], @@ -301,7 +310,7 @@ @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', 'repository.admin') def create(self, repo_name): - repo = RepoModel()._get_repo(repo_name) + repo = c.db_repo try: _form = PullRequestForm(repo.repo_id)().to_python(request.POST) except formencode.Invalid as errors: @@ -319,9 +328,8 @@ org_ref_name, org_rev) = org_ref.split(':') if org_ref_type == 'rev': - org_ref_type = 'branch' cs = org_repo.scm_instance.get_changeset(org_rev) - org_ref = '%s:%s:%s' % (org_ref_type, cs.branch, cs.raw_id) + org_ref = 'branch:%s:%s' % (cs.branch, cs.raw_id) other_repo_name = _form['other_repo'] other_ref = _form['other_ref'] # will have symbolic name and head revision @@ -329,21 +337,38 @@ (other_ref_type, other_ref_name, other_rev) = other_ref.split(':') + if other_ref_type == 'rev': + cs = other_repo.scm_instance.get_changeset(other_rev) + other_ref_name = cs.raw_id[:12] + other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, cs.raw_id) - cs_ranges, _cs_ranges_not, ancestor_rev = \ + cs_ranges, _cs_ranges_not, ancestor_revs = \ CompareController._get_changesets(org_repo.scm_instance.alias, other_repo.scm_instance, other_rev, # org and other "swapped" org_repo.scm_instance, org_rev, ) + ancestor_rev = msg = None + if not cs_ranges: + msg = _('Cannot create empty pull request') + elif not ancestor_revs: + ancestor_rev = org_repo.scm_instance.EMPTY_CHANGESET + elif len(ancestor_revs) == 1: + ancestor_rev = ancestor_revs[0] + else: + msg = _('Cannot create pull request - criss cross merge detected, please merge a later %s revision to %s' + ) % (other_ref_name, org_ref_name) if ancestor_rev is None: - ancestor_rev = org_repo.scm_instance.EMPTY_CHANGESET + h.flash(msg, category='error') + log.error(msg) + raise HTTPNotFound + revisions = [cs_.raw_id for cs_ in cs_ranges] # hack: ancestor_rev is not an other_rev but we want to show the # requested destination and have the exact ancestor other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev) - reviewers = _form['review_members'] + reviewer_ids = [] title = _form['pullrequest_title'] if not title: @@ -355,10 +380,10 @@ other_repo_name, h.short_ref(other_ref_type, other_ref_name)) description = _form['pullrequest_desc'].strip() or _('No description') try: + created_by = User.get(self.authuser.user_id) pull_request = PullRequestModel().create( - self.authuser.user_id, org_repo_name, org_ref, other_repo_name, - other_ref, revisions, reviewers, title, description - ) + created_by, org_repo, org_ref, other_repo, other_ref, revisions, + title, description, reviewer_ids) Session().commit() h.flash(_('Successfully opened new pull request'), category='success') @@ -369,59 +394,73 @@ h.flash(_('Error occurred while creating pull request'), category='error') log.error(traceback.format_exc()) - return redirect(url('pullrequest_home', repo_name=repo_name)) + raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name)) - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) - def create_update(self, old_pull_request, updaterev, title, description, reviewers_ids): - org_repo = RepoModel()._get_repo(old_pull_request.org_repo.repo_name) + def create_new_iteration(self, old_pull_request, new_rev, title, description, reviewer_ids): + org_repo = old_pull_request.org_repo org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(':') - new_org_rev = self._get_ref_rev(org_repo, 'rev', updaterev) + new_org_rev = self._get_ref_rev(org_repo, 'rev', new_rev) - other_repo = RepoModel()._get_repo(old_pull_request.other_repo.repo_name) + other_repo = old_pull_request.other_repo other_ref_type, other_ref_name, other_rev = old_pull_request.other_ref.split(':') # other_rev is ancestor #assert other_ref_type == 'branch', other_ref_type # TODO: what if not? new_other_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name) - cs_ranges, _cs_ranges_not, ancestor_rev = CompareController._get_changesets(org_repo.scm_instance.alias, + cs_ranges, _cs_ranges_not, ancestor_revs = CompareController._get_changesets(org_repo.scm_instance.alias, other_repo.scm_instance, new_other_rev, # org and other "swapped" org_repo.scm_instance, new_org_rev) + ancestor_rev = msg = None + if not cs_ranges: + msg = _('Cannot create empty pull request update') # cannot happen! + elif not ancestor_revs: + msg = _('Cannot create pull request update - no common ancestor found') # cannot happen + elif len(ancestor_revs) == 1: + ancestor_rev = ancestor_revs[0] + else: + msg = _('Cannot create pull request update - criss cross merge detected, please merge a later %s revision to %s' + ) % (other_ref_name, org_ref_name) + if ancestor_rev is None: + h.flash(msg, category='error') + log.error(msg) + raise HTTPNotFound old_revisions = set(old_pull_request.revisions) revisions = [cs.raw_id for cs in cs_ranges] new_revisions = [r for r in revisions if r not in old_revisions] lost = old_revisions.difference(revisions) - infos = ['This is an update of %s "%s".' % + infos = ['This is a new iteration of %s "%s".' % (h.canonical_url('pullrequest_show', repo_name=old_pull_request.other_repo.repo_name, pull_request_id=old_pull_request.pull_request_id), old_pull_request.title)] if lost: - infos.append(_('Missing changesets since the previous pull request:')) + infos.append(_('Missing changesets since the previous iteration:')) for r in old_pull_request.revisions: if r in lost: rev_desc = org_repo.get_changeset(r).message.split('\n')[0] - infos.append(' %s "%s"' % (h.short_id(r), rev_desc)) + infos.append(' %s %s' % (h.short_id(r), rev_desc)) if new_revisions: - infos.append(_('New changesets on %s %s since the previous pull request:') % (org_ref_type, org_ref_name)) + infos.append(_('New changesets on %s %s since the previous iteration:') % (org_ref_type, org_ref_name)) for r in reversed(revisions): if r in new_revisions: rev_desc = org_repo.get_changeset(r).message.split('\n')[0] infos.append(' %s %s' % (h.short_id(r), h.shorter(rev_desc, 80))) if ancestor_rev == other_rev: - infos.append(_("Ancestor didn't change - show diff since previous version:")) + infos.append(_("Ancestor didn't change - diff since previous iteration:")) infos.append(h.canonical_url('compare_url', repo_name=org_repo.repo_name, # other_repo is always same as repo_name org_ref_type='rev', org_ref_name=h.short_id(org_rev), # use old org_rev as base other_ref_type='rev', other_ref_name=h.short_id(new_org_rev), )) # note: linear diff, merge or not doesn't matter else: - infos.append(_('This pull request is based on another %s revision and there is no simple diff.') % other_ref_name) + infos.append(_('This iteration is based on another %s revision and there is no simple diff.') % other_ref_name) else: - infos.append(_('No changes found on %s %s since previous version.') % (org_ref_type, org_ref_name)) + infos.append(_('No changes found on %s %s since previous iteration.') % (org_ref_type, org_ref_name)) # TODO: fail? # hack: ancestor_rev is not an other_ref but we want to show the @@ -436,19 +475,17 @@ v = 2 title = '%s (v%s)' % (title.strip(), v) - # using a mail-like separator, insert new update info at the top of the list + # using a mail-like separator, insert new iteration info in description with latest first descriptions = description.replace('\r\n', '\n').split('\n-- \n', 1) description = descriptions[0].strip() + '\n\n-- \n' + '\n'.join(infos) if len(descriptions) > 1: description += '\n\n' + descriptions[1].strip() try: + created_by = User.get(self.authuser.user_id) pull_request = PullRequestModel().create( - self.authuser.user_id, - old_pull_request.org_repo.repo_name, new_org_ref, - old_pull_request.other_repo.repo_name, new_other_ref, - revisions, reviewers_ids, title, description - ) + created_by, org_repo, new_org_ref, other_repo, new_other_ref, revisions, + title, description, reviewer_ids) except UserInvalidException as u: h.flash(_('Invalid reviewer "%s" specified') % u, category='error') raise HTTPBadRequest() @@ -456,21 +493,21 @@ h.flash(_('Error occurred while creating pull request'), category='error') log.error(traceback.format_exc()) - return redirect(old_pull_request.url()) + raise HTTPFound(location=old_pull_request.url()) ChangesetCommentsModel().create( - text=_('Closed, replaced by %s .') % pull_request.url(canonical=True), - repo=old_pull_request.other_repo.repo_id, - user=c.authuser.user_id, + text=_('Closed, next iteration: %s .') % pull_request.url(canonical=True), + repo=old_pull_request.other_repo_id, + author=c.authuser.user_id, pull_request=old_pull_request.pull_request_id, closing_pr=True) PullRequestModel().close_pull_request(old_pull_request.pull_request_id) Session().commit() - h.flash(_('Pull request update created'), + h.flash(_('New pull request iteration created'), category='success') - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) # pullrequest_post for PR editing @LoginRequired() @@ -483,29 +520,45 @@ raise HTTPForbidden() assert pull_request.other_repo.repo_name == repo_name #only owner or admin can update it - owner = pull_request.owner.user_id == c.authuser.user_id + owner = pull_request.owner_id == c.authuser.user_id repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) if not (h.HasPermissionAny('hg.admin')() or repo_admin or owner): raise HTTPForbidden() _form = PullRequestPostForm()().to_python(request.POST) - reviewers_ids = [int(s) for s in _form['review_members']] + reviewer_ids = set(int(s) for s in _form['review_members']) + + org_reviewer_ids = set(int(s) for s in _form['org_review_members']) + current_reviewer_ids = set(prr.user_id for prr in pull_request.reviewers) + other_added = [User.get(u) for u in current_reviewer_ids - org_reviewer_ids] + other_removed = [User.get(u) for u in org_reviewer_ids - current_reviewer_ids] + if other_added: + h.flash(_('Meanwhile, the following reviewers have been added: %s') % + (', '.join(u.username for u in other_added)), + category='warning') + if other_removed: + h.flash(_('Meanwhile, the following reviewers have been removed: %s') % + (', '.join(u.username for u in other_removed)), + category='warning') if _form['updaterev']: - return self.create_update(pull_request, + return self.create_new_iteration(pull_request, _form['updaterev'], _form['pullrequest_title'], _form['pullrequest_desc'], - reviewers_ids) + reviewer_ids) old_description = pull_request.description pull_request.title = _form['pullrequest_title'] pull_request.description = _form['pullrequest_desc'].strip() or _('No description') pull_request.owner = User.get_by_username(_form['owner']) user = User.get(c.authuser.user_id) + add_reviewer_ids = reviewer_ids - org_reviewer_ids - current_reviewer_ids + remove_reviewer_ids = (org_reviewer_ids - reviewer_ids) & current_reviewer_ids try: PullRequestModel().mention_from_description(user, pull_request, old_description) - PullRequestModel().update_reviewers(user, pull_request_id, reviewers_ids) + PullRequestModel().add_reviewers(user, pull_request, add_reviewer_ids) + PullRequestModel().remove_reviewers(user, pull_request, remove_reviewer_ids) except UserInvalidException as u: h.flash(_('Invalid reviewer "%s" specified') % u, category='error') raise HTTPBadRequest() @@ -513,7 +566,7 @@ Session().commit() h.flash(_('Pull request updated'), category='success') - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) @LoginRequired() @NotAnonymous() @@ -523,12 +576,12 @@ 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.owner.user_id == c.authuser.user_id: + if pull_request.owner_id == c.authuser.user_id: PullRequestModel().delete(pull_request) Session().commit() h.flash(_('Successfully deleted pull request'), category='success') - return redirect(url('my_pullrequests')) + raise HTTPFound(location=url('my_pullrequests')) raise HTTPForbidden() @LoginRequired() @@ -557,80 +610,98 @@ c.a_repo = c.pull_request.other_repo (c.a_ref_type, c.a_ref_name, - c.a_rev) = c.pull_request.other_ref.split(':') # other_rev is ancestor + c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!! c.cs_repo = c.cs_repo - c.cs_ranges = [org_scm_instance.get_changeset(x) for x in c.pull_request.revisions] + try: + c.cs_ranges = [org_scm_instance.get_changeset(x) + for x in c.pull_request.revisions] + except ChangesetDoesNotExistError: + c.cs_ranges = [] + h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name), + 'error') c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ... revs = [ctx.revision for ctx in reversed(c.cs_ranges)] c.jsdata = json.dumps(graph_data(org_scm_instance, revs)) c.is_range = False - if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor - cs_a = org_scm_instance.get_changeset(c.a_rev) - root_parents = c.cs_ranges[0].parents - c.is_range = cs_a in root_parents - #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning + try: + if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor + cs_a = org_scm_instance.get_changeset(c.a_rev) + root_parents = c.cs_ranges[0].parents + c.is_range = cs_a in root_parents + #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning + except ChangesetDoesNotExistError: # probably because c.a_rev not found + pass + except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing + pass avail_revs = set() avail_show = [] c.cs_branch_name = c.cs_ref_name + c.a_branch_name = None other_scm_instance = c.a_repo.scm_instance c.update_msg = "" c.update_msg_other = "" - if org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor': - if c.cs_ref_type != 'branch': - c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ? - c.a_branch_name = c.a_ref_name - if c.a_ref_type != 'branch': - try: - c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ? - except EmptyRepositoryError: - c.a_branch_name = 'null' # not a branch name ... but close enough - # candidates: descendants of old head that are on the right branch - # and not are the old head itself ... - # and nothing at all if old head is a descendant of target ref name - if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name): - c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name - elif c.pull_request.is_closed(): - c.update_msg = _('This pull request has been closed and can not be updated.') - else: # look for descendants of PR head on source branch in org repo - avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)', - revs[0], c.cs_branch_name) - if len(avail_revs) > 1: # more than just revs[0] - # also show changesets that not are descendants but would be merged in - targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id - if org_scm_instance.path != other_scm_instance.path: - # Note: org_scm_instance.path must come first so all - # valid revision numbers are 100% org_scm compatible - # - both for avail_revs and for revset results - hgrepo = unionrepo.unionrepository(org_scm_instance.baseui, - org_scm_instance.path, - other_scm_instance.path) + try: + if not c.cs_ranges: + c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev + elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor': + if c.cs_ref_type != 'branch': + c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ? + c.a_branch_name = c.a_ref_name + if c.a_ref_type != 'branch': + try: + c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ? + except EmptyRepositoryError: + c.a_branch_name = 'null' # not a branch name ... but close enough + # candidates: descendants of old head that are on the right branch + # and not are the old head itself ... + # and nothing at all if old head is a descendant of target ref name + if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name): + c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name + elif c.pull_request.is_closed(): + c.update_msg = _('This pull request has been closed and can not be updated.') + else: # look for descendants of PR head on source branch in org repo + avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)', + revs[0], c.cs_branch_name) + if len(avail_revs) > 1: # more than just revs[0] + # also show changesets that not are descendants but would be merged in + targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id + if org_scm_instance.path != other_scm_instance.path: + # Note: org_scm_instance.path must come first so all + # valid revision numbers are 100% org_scm compatible + # - both for avail_revs and for revset results + hgrepo = unionrepo.unionrepository(org_scm_instance.baseui, + org_scm_instance.path, + other_scm_instance.path) + else: + hgrepo = org_scm_instance._repo + show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s', + avail_revs, revs[0], targethead)) + c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name else: - hgrepo = org_scm_instance._repo - show = set(hgrepo.revs('::%ld & !::%s & !::%s', - avail_revs, revs[0], targethead)) - c.update_msg = _('This pull request can be updated with changes on %s:') % c.cs_branch_name - else: - show = set() - avail_revs = set() # drop revs[0] - c.update_msg = _('No changesets found for updating this pull request.') + show = set() + avail_revs = set() # drop revs[0] + c.update_msg = _('No additional changesets found for iterating on this pull request.') - # TODO: handle branch heads that not are tip-most - brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0]) - if brevs: - # also show changesets that are on branch but neither ancestors nor descendants - show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name)) - show.add(revs[0]) # make sure graph shows this so we can see how they relate - c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name, - h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id)) + # TODO: handle branch heads that not are tip-most + brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0]) + if brevs: + # also show changesets that are on branch but neither ancestors nor descendants + show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name)) + show.add(revs[0]) # make sure graph shows this so we can see how they relate + c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name, + h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id)) - avail_show = sorted(show, reverse=True) + avail_show = sorted(show, reverse=True) - elif org_scm_instance.alias == 'git': - c.update_msg = _("Git pull requests don't support updates yet.") + elif org_scm_instance.alias == 'git': + c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not + c.update_msg = _("Git pull requests don't support iterating yet.") + except ChangesetDoesNotExistError: + c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev c.avail_revs = avail_revs c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show] @@ -641,7 +712,7 @@ c.statuses = c.cs_repo.statuses(raw_ids) ignore_whitespace = request.GET.get('ignorews') == '1' - line_context = request.GET.get('context', 3) + line_context = safe_int(request.GET.get('context'), 3) c.ignorews_url = _ignorews_url c.context_url = _context_url c.fulldiff = request.GET.get('fulldiff') @@ -650,10 +721,12 @@ # we swap org/other ref since we run a simple diff on one repo log.debug('running diff between %s and %s in %s', c.a_rev, c.cs_rev, org_scm_instance.path) - txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev), - ignore_whitespace=ignore_whitespace, - context=line_context) - + try: + txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev), + ignore_whitespace=ignore_whitespace, + context=line_context) + except ChangesetDoesNotExistError: + txtdiff = _("The diff can't be shown - the PR revisions could not be found.") diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff', diff_limit=diff_limit) _parsed = diff_processor.prepare() @@ -662,8 +735,7 @@ if isinstance(_parsed, LimitedDiffContainer): c.limited_diff = True - c.files = [] - c.changes = {} + c.file_diff_data = [] c.lines_added = 0 c.lines_deleted = 0 @@ -671,11 +743,11 @@ st = f['stats'] c.lines_added += st['added'] c.lines_deleted += st['deleted'] - fid = h.FID('', f['filename']) - c.files.append([fid, f['operation'], f['filename'], f['stats']]) - htmldiff = diff_processor.as_html(enable_comments=True, - parsed_lines=[f]) - c.changes[fid] = [f['operation'], f['filename'], htmldiff] + filename = f['filename'] + fid = h.FID('', filename) + diff = diff_processor.as_html(enable_comments=True, + parsed_lines=[f]) + c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, diff, st)) # inline comments c.inline_cnt = 0 @@ -698,7 +770,7 @@ c.changeset_statuses = ChangesetStatus.STATUSES c.as_form = False - c.ancestor = None # there is one - but right here we don't know which + c.ancestors = None # [c.a_rev] ... but that is shown in an other way return render('/pullrequests/pullrequest_show.html') @LoginRequired() @@ -711,10 +783,11 @@ status = request.POST.get('changeset_status') close_pr = request.POST.get('save_close') + delete = request.POST.get('save_delete') f_path = request.POST.get('f_path') line_no = request.POST.get('line') - if (status or close_pr) and (f_path or line_no): + if (status or close_pr or delete) and (f_path or line_no): # status votes and closing is only possible in general comments raise HTTPBadRequest() @@ -724,20 +797,31 @@ h.flash(_('No permission to change pull request status'), 'error') raise HTTPForbidden() - text = request.POST.get('text', '').strip() - if close_pr: - text = _('Closing.') + '\n' + text + if delete == "delete": + if (pull_request.owner_id == c.authuser.user_id or + h.HasPermissionAny('hg.admin')() or + h.HasRepoPermissionAny('repository.admin')(pull_request.org_repo.repo_name) or + h.HasRepoPermissionAny('repository.admin')(pull_request.other_repo.repo_name) + ) and not pull_request.is_closed(): + PullRequestModel().delete(pull_request) + Session().commit() + h.flash(_('Successfully deleted pull request %s') % pull_request_id, + category='success') + return { + 'location': url('my_pullrequests'), # or repo pr list? + } + raise HTTPFound(location=url('my_pullrequests')) # or repo pr list? + raise HTTPForbidden() - comment = ChangesetCommentsModel().create( - text=text, - repo=c.db_repo.repo_id, - user=c.authuser.user_id, - pull_request=pull_request_id, + text = request.POST.get('text', '').strip() + + comment = create_comment( + text, + status, + pull_request_id=pull_request_id, f_path=f_path, line_no=line_no, - status_change=(ChangesetStatus.get_status_lbl(status) - if status and allowed_to_change_status else None), - closing_pr=close_pr + closing_pr=close_pr, ) action_logger(self.authuser, @@ -762,7 +846,7 @@ Session().commit() if not request.environ.get('HTTP_X_PARTIAL_XHR'): - return redirect(pull_request.url()) + raise HTTPFound(location=pull_request.url()) data = { 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))), @@ -786,7 +870,7 @@ #don't allow deleting comments on closed pull request raise HTTPForbidden() - owner = co.author.user_id == c.authuser.user_id + owner = co.author_id == c.authuser.user_id repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name) if h.HasPermissionAny('hg.admin')() or repo_admin or owner: ChangesetCommentsModel().delete(comment=co) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/search.py --- a/kallithea/controllers/search.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/search.py Sat Dec 24 00:34:38 2016 +0100 @@ -40,9 +40,9 @@ from kallithea.lib.base import BaseRepoController, render from kallithea.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \ IDX_NAME, WhooshResultWrapper -from kallithea.model.repo import RepoModel +from kallithea.lib.page import Page from kallithea.lib.utils2 import safe_str, safe_int -from kallithea.lib.helpers import Page +from kallithea.model.repo import RepoModel log = logging.getLogger(__name__) @@ -85,7 +85,7 @@ log.debug(cur_query) if c.cur_query: - p = safe_int(request.GET.get('page', 1), 1) + p = safe_int(request.GET.get('page'), 1) highlight_items = set() try: idx = open_dir(config['app_conf']['index_dir'], @@ -139,7 +139,7 @@ log.error('Empty Index data') c.runtime = _('There is no index to search in. ' 'Please run whoosh indexer') - except (Exception): + except Exception: log.error(traceback.format_exc()) c.runtime = _('An error occurred during search operation.') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/summary.py --- a/kallithea/controllers/summary.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/controllers/summary.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,6 +28,7 @@ import traceback import calendar import logging +import itertools from time import mktime from datetime import timedelta, date @@ -37,19 +38,17 @@ from beaker.cache import cache_region, region_invalidate -from kallithea.lib.compat import product from kallithea.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \ NodeDoesNotExistError from kallithea.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP from kallithea.model.db import Statistics, CacheInvalidation, User from kallithea.lib.utils import jsonify from kallithea.lib.utils2 import safe_str -from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\ +from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ NotAnonymous from kallithea.lib.base import BaseRepoController, render from kallithea.lib.vcs.backends.base import EmptyChangeset from kallithea.lib.markup_renderer import MarkupRenderer -from kallithea.lib.celerylib import run_task from kallithea.lib.celerylib.tasks import get_commits_stats from kallithea.lib.compat import json from kallithea.lib.vcs.nodes import FileNode @@ -58,7 +57,7 @@ log = logging.getLogger(__name__) README_FILES = [''.join([x[0][0], x[1][0]]) for x in - sorted(list(product(ALL_READMES, ALL_EXTS)), + sorted(list(itertools.product(ALL_READMES, ALL_EXTS)), key=lambda y:y[0][1] + y[1][1])] @@ -71,7 +70,7 @@ repo_name = db_repo.repo_name log.debug('Looking for README file') - @cache_region('long_term') + @cache_region('long_term', '_get_readme_from_cache') def _get_readme_from_cache(key, kind): readme_data = None readme_file = None @@ -105,7 +104,7 @@ kind = 'README' valid = CacheInvalidation.test_and_set_valid(repo_name, kind) if not valid: - region_invalidate(_get_readme_from_cache, None, repo_name, kind) + region_invalidate(_get_readme_from_cache, None, '_get_readme_from_cache', repo_name, kind) return _get_readme_from_cache(repo_name, kind) @LoginRequired() @@ -114,8 +113,9 @@ def index(self, repo_name): _load_changelog_summary() - username = '' - if self.authuser.username != User.DEFAULT_USER: + if self.authuser.is_default_user: + username = '' + else: username = safe_str(self.authuser.username) _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl @@ -134,8 +134,8 @@ else: c.show_stats = False - stats = self.sa.query(Statistics)\ - .filter(Statistics.repository == c.db_repo)\ + stats = self.sa.query(Statistics) \ + .filter(Statistics.repository == c.db_repo) \ .scalar() c.stats_percentage = 0 @@ -192,8 +192,8 @@ c.ts_min = ts_min_m c.ts_max = ts_max_y - stats = self.sa.query(Statistics)\ - .filter(Statistics.repository == c.db_repo)\ + stats = self.sa.query(Statistics) \ + .filter(Statistics.repository == c.db_repo) \ .scalar() c.stats_percentage = 0 if stats and stats.languages: @@ -210,7 +210,7 @@ sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10] ) last_rev = stats.stat_on_revision + 1 - c.repo_last_rev = c.db_repo_scm_instance.count()\ + c.repo_last_rev = c.db_repo_scm_instance.count() \ if c.db_repo_scm_instance.revisions else 0 if last_rev == 0 or c.repo_last_rev == 0: pass @@ -224,6 +224,5 @@ c.no_data = True recurse_limit = 500 # don't recurse more than 500 times when parsing - run_task(get_commits_stats, c.db_repo.repo_name, ts_min_y, - ts_max_y, recurse_limit) + get_commits_stats(c.db_repo.repo_name, ts_min_y, ts_max_y, recurse_limit) return render('summary/statistics.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/controllers/tags.py --- a/kallithea/controllers/tags.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.controllers.tags -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tags controller for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 21, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. - -""" - -import logging - -from pylons import tmpl_context as c - -from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator -from kallithea.lib.base import BaseRepoController, render -from kallithea.lib.compat import OrderedDict - -log = logging.getLogger(__name__) - - -class TagsController(BaseRepoController): - - def __before__(self): - super(TagsController, self).__before__() - - @LoginRequired() - @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', - 'repository.admin') - def index(self): - c.repo_tags = OrderedDict() - - tags = [(name, c.db_repo_scm_instance.get_changeset(hash_)) for \ - name, hash_ in c.db_repo_scm_instance.tags.items()] - ordered_tags = sorted(tags, key=lambda x: x[1].date, reverse=True) - for name, cs_tag in ordered_tags: - c.repo_tags[name] = cs_tag - - return render('tags/tags.html') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/be/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/be/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/be/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -1,14 +1,14 @@ # Belarusian translations for Kallithea. -# Copyright (C) 2015 Various authors, licensing as GPLv3 +# Copyright (C) 2016 Various authors, licensing as GPLv3 # This file is distributed under the same license as the Kallithea project. -# Automatically generated, 2015. +# Automatically generated, 2016. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" -"PO-Revision-Date: 2015-08-08 12:03+0300\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" +"PO-Revision-Date: 2016-02-24 16:36+0100\n" "Last-Translator: Andrew Shadura \n" "Language-Team: Belarusian " "\n" @@ -18,14 +18,14 @@ "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=" "4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 2.4-dev\n" - -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +"X-Generator: Weblate 2.5-dev\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Яшчэ не было змен" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -37,35 +37,29 @@ msgid "None" msgstr "Нічога" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(зачынена)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" -msgstr "Адлюстроўваць прабелы" - -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +msgstr "Паказваць прабелы" + +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "Ігнараваць прабелы" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "Павялічыць кантэкст да %(num)s радкоў" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "Няма такой рэвізіі ў гэтым рэпазітары" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "Нельга рэдагаваць статус змен, злучаных з зачыненымі pull-request'ами" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "Выбраць набор змен" @@ -119,125 +113,127 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." -msgstr "Змены апынуліся занадта вялікімі і былі выразаныя..." +msgstr "Змены апынуліся занадта вялікімі і былі скарочаныя..." #: kallithea/controllers/feed.py:91 #, python-format msgid "%s committed on %s" msgstr "%s выканаў каміт у %s" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "Націсніце каб дадаць новы файл" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "Няма файлаў. %s" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "%s (%s)" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "Рэпазітар заблакаваў %s у %s" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "Вы можаце выдаляць файлы толькі ў рэвізіі, злучанай з існай галінкай " - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +#, fuzzy +msgid "You can only delete files with revision being a valid branch" +msgstr "Вы можаце выдаляць файлы толькі ў рэвізіі, злучанай з існай галінай " + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "Файл %s выдалены з дапамогай Kallithea" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "Файл %s выдалены" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "Падчас каміта адбылася памылка" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "Вы можаце рэдагаваць файлы толькі ў рэвізіі, злучанай з існай галінкай " - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +#, fuzzy +msgid "You can only edit files with revision being a valid branch" +msgstr "Вы можаце рэдагаваць файлы толькі ў рэвізіі, злучанай з існай галінай " + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "Файл %s адрэдагаваны з дапамогай Kallithea" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Без змен" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" -msgstr "Змены ўжыты ў %s" - -#: kallithea/controllers/files.py:443 +msgstr "Змены захаваныя ў %s" + +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Файл дададзены з дапамогай Kallithea" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Пуста" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "Безназоўны" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" "Размяшчэнне павінна быць адносным шляхам, і не можа ўтрымліваць \"..\" у " "шляхі" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" -msgstr "Магчымасць спампоўваць адключана" - -#: kallithea/controllers/files.py:537 +msgstr "Магчымасць спампоўваць адключаная" + +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Невядомая рэвізія %s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Пусты рэпазітар" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "Невядомы тып архіва" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Набор змен" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" -msgstr "Галінкі" - -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +msgstr "Галіны" + +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Тэгі" @@ -245,13 +241,13 @@ #: kallithea/controllers/forks.py:186 #, python-format msgid "An error occurred during repository forking %s" -msgstr "Адбылася памылка падчас стварэння форка рэпазітара %s" +msgstr "Памылка падчас стварэння форка рэпазітара %s" #: kallithea/controllers/home.py:84 msgid "Groups" msgstr "Групы" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -259,23 +255,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Рэпазітары" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" -msgstr "Галінка" - -#: kallithea/controllers/home.py:136 +msgstr "Галіна" + +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "Зачыненыя галіны" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "Тэгі" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "Закладкі" @@ -283,165 +283,166 @@ #: kallithea/templates/journal/public_journal.html:4 #: kallithea/templates/journal/public_journal.html:21 msgid "Public Journal" -msgstr "Публічны часопіс" +msgstr "Публічны журнал" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" -msgstr "Часопіс" - -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +msgstr "Журнал" + +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "Няслушная капча" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "Рэгістрацыя ў Kallithea прайшла паспяхова" - -#: kallithea/controllers/login.py:202 -#, fuzzy -#| msgid "Your password reset link was sent" +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "Рэгістрацыя ў %s прайшла паспяхова" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" -msgstr "Спасылка для скідання пароля адпраўлена" - -#: kallithea/controllers/login.py:251 -#, fuzzy -#| msgid "Password reset link" +msgstr "Код для скідання пароля адпраўлены" + +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" -msgstr "Спасылка скіду пароля" - -#: kallithea/controllers/login.py:256 +msgstr "Няслушны код скідання пароля" + +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "Пароль абноўлены" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (зачынена)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "Змены" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "Адмысловы" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" -msgstr "Галінкі ўдзельніка" - -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +msgstr "Галіны ўдзельніка" + +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Закладкі" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "Памылка пры стварэнні pull-запыту: %s" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "Няма апісання" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "Pull-запыт створаны паспяхова" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" -msgstr "" - -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +msgstr "Няслушны рэцэнзент \"%s\"" + +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "Адбылася памылка пры стварэнні pull-запыту" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "Адсутныя рэвізіі адносна папярэдняга pull-запыту:" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "Новыя рэвізіі на %s %s адносна папярэдняга pull-запыту:" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "Гэты pull-запыт заснаваны на іншай рэвізіі %s, просты diff немагчымы." -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "Няма змен на %s %s адносна папярэдняй версіі." -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." -msgstr "Зачынены, замешчаны %s." - -#: kallithea/controllers/pullrequests.py:470 +msgstr "Зачынены, заменены %s." + +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "Абнаўленне для pull-запыту створана" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "Pull-запыт абноўлены" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "Pull-запыт паспяхова выдалены" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." -msgstr "Гэты pull-запыт ужо прыняты на галінку %s." - -#: kallithea/controllers/pullrequests.py:596 +msgstr "Гэты pull-запыт ужо прыняты на галіну %s." + +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "Гэты pull-запыт быў зачынены і не можа быць абноўлены." -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "Гэты pull-запыт можа быць абноўлены з %s:" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "Гэтыя змены даступныя на %s:" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "Няма змен для абнаўлення гэтага pull-запыту." -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." -msgstr "Увага: Галінка %s мае яшчэ адну верхавіну: %s." - -#: kallithea/controllers/pullrequests.py:631 +msgstr "Увага: Галіна %s мае яшчэ адну верхавіну: %s." + +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." -msgstr "Абнаўленне pull-запытаў git не падтрымліваецца." - -#: kallithea/controllers/pullrequests.py:722 +msgstr "Абнаўленне pull-запытаў git яшчэ не падтрымліваецца." + +#: kallithea/controllers/pullrequests.py:727 msgid "No permission to change pull request status" msgstr "Няма правоў змяняць статус pull-запыту" -#: kallithea/controllers/pullrequests.py:727 +#: kallithea/controllers/pullrequests.py:738 +#, python-format +msgid "Successfully deleted pull request %s" +msgstr "Pull-запыт %s паспяхова выдалены" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "Зачынены." @@ -455,14 +456,14 @@ #: kallithea/controllers/search.py:144 msgid "An error occurred during search operation." -msgstr "Адбылася памылка пры выкананні гэтага пошуку." - -#: kallithea/controllers/summary.py:180 +msgstr "Памылка пры выкананні гэтага пошуку." + +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "Няма дадзеных" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "Статыстычныя дадзеныя адключаны для гэтага рэпазітара" @@ -473,7 +474,7 @@ #: kallithea/controllers/admin/auth_settings.py:146 msgid "error occurred during update of auth settings" -msgstr "адбылася памылка пры абнаўленні налад аўтарызацыі" +msgstr "памылка пры абнаўленні налад аўтарызацыі" #: kallithea/controllers/admin/defaults.py:97 msgid "Default settings updated successfully" @@ -481,119 +482,119 @@ #: kallithea/controllers/admin/defaults.py:112 msgid "Error occurred during update of defaults" -msgstr "Адбылася памылка пры абнаўленні стандартных налад" +msgstr "Памылка пры абнаўленні стандартных налад" + +#: kallithea/controllers/admin/gists.py:58 +#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "Назаўжды" #: kallithea/controllers/admin/gists.py:59 -#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/my_account.py:244 #: kallithea/controllers/admin/users.py:285 -msgid "Forever" -msgstr "Назаўжды" +msgid "5 minutes" +msgstr "5 хвілін" #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" -msgstr "5 хвілін" +msgid "1 hour" +msgstr "1 гадзіна" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "1 гадзіна" +msgid "1 day" +msgstr "1 дзень" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "1 дзень" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "1 месяц" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "Тэрмін" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Адбылася памылка падчас стварэння gist-запіса" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "Gist-запіс %s выдалены" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "Без змен" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" -msgstr "" - -#: kallithea/controllers/admin/gists.py:267 +msgstr "Gist-запіс абноўлены" + +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" -msgstr "Дадзеныя gist-запісы абноўлены" - -#: kallithea/controllers/admin/gists.py:270 +msgstr "Gist-запіс абноўлены" + +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" -msgstr "Адбылася памылка пры абнаўленні gist-запісы %s" +msgstr "Памылка пры абнаўленні gist-запісу %s" #: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215 #: kallithea/model/user.py:237 msgid "You can't edit this user since it's crucial for entire application" msgstr "" -"Вы не можаце змяніць дадзеныя гэтага карыстача, паколькі ён важны для " -"працы ўсяго прыкладання" +"Вы не можаце змяніць дадзеныя гэтага карыстальніка, паколькі ён важны для" +" працы ўсёй праграмы" #: kallithea/controllers/admin/my_account.py:129 msgid "Your account was updated successfully" msgstr "Ваш уліковы запіс паспяхова абноўлены" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" -msgstr "Адбылася памылка пры абнаўленні карыстача %s" +msgstr "Памылка пры абнаўленні карыстальніка %s" #: kallithea/controllers/admin/my_account.py:178 msgid "Error occurred during update of user password" msgstr "Памылка пры абнаўленні пароля" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" -msgstr "Карыстачу дададзены e-mail %s" +msgstr "Карыстальніку дададзены e-mail %s" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" -msgstr "Адбылася памылка пры захаванні e-mail" +msgstr "Памылка пры захаванні e-mail" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" -msgstr "E-mail карыстача выдалены" +msgstr "E-mail карыстальніка выдалены" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "API-ключ паспяхова створаны" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "API-ключ паспяхова скінуты" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "API-ключ паспяхова выдалены" @@ -643,10 +644,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "Адміністратар" @@ -677,7 +678,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "Ручная актывацыя вонкавага ўліковага запісу" @@ -689,7 +690,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "Аўтаматычная актывацыя вонкавага ўліковага запісу" @@ -704,360 +705,364 @@ #: kallithea/controllers/admin/permissions.py:124 msgid "Global permissions updated successfully" -msgstr "Глабальныя прывілеі паспяхова абноўлены" +msgstr "Глабальныя прывілеі паспяхова абноўленыя" #: kallithea/controllers/admin/permissions.py:139 msgid "Error occurred during update of permissions" msgstr "Адбылася памылка падчас абнаўлення прывілеяў" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "Адбылася памылка пры стварэнні групы рэпазітароў %s" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" -msgstr "Створана новая група рэпазітароў %s" - -#: kallithea/controllers/admin/repo_groups.py:250 +msgstr "Створаная новая група рэпазітароў %s" + +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" -msgstr "Група рэпазітароў %s абноўлена" - -#: kallithea/controllers/admin/repo_groups.py:266 +msgstr "Група рэпазітароў %s абноўленая" + +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "Адбылася памылка пры абнаўленні групы рэпазітароў %s" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" -msgstr "Дадзеная група ўтрымоўвае %s рэпазітароў і не можа быць выдалена" - -#: kallithea/controllers/admin/repo_groups.py:291 +msgstr "Група ўтрымлівае %s рэпазітароў і не можа быць выдаленая" + +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" -msgstr "Група ўтрымоўвае ў сабе %s падгруп і не можа быць выдалены" - -#: kallithea/controllers/admin/repo_groups.py:297 +msgstr "Група ўтрымлівае ў сабе %s падгруп і не можа быць выдаленая" + +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" -msgstr "Група рэпазітароў %s выдалена" - -#: kallithea/controllers/admin/repo_groups.py:302 +msgstr "Група рэпазітароў %s выдаленая" + +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" -msgstr "Адбылася памылка пры выдаленні групы рэпазітароў %s" - -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +msgstr "Памылка пры выдаленні групы рэпазітароў %s" + +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "Адміністратар не можа адклікаць свае прывелеі" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" -msgstr "Прывілеі групы рэпазітароў абноўлены" - -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +msgstr "Прывілеі групы рэпазітароў абноўленыя" + +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" -msgstr "Адбылася памылка пры водгуку прывелеі" - -#: kallithea/controllers/admin/repos.py:152 +msgstr "Памылка пры водгуку прывелея" + +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" -msgstr "Адбылася памылка пры стварэнні рэпазітара %s" - -#: kallithea/controllers/admin/repos.py:213 +msgstr "Памылка пры стварэнні рэпазітара %s" + +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "Рэпазітар %s створаны з %s" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" -msgstr "Зроблены форк(копія) рэпазітара %s на %s" - -#: kallithea/controllers/admin/repos.py:225 +msgstr "Зроблены форк рэпазітара %s на %s" + +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "Рэпазітар %s створаны" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "Рэпазітар %s паспяхова абноўлены" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" -msgstr "Адбылася памылка падчас абнаўлення рэпазітара %s" - -#: kallithea/controllers/admin/repos.py:310 +msgstr "Памылка падчас абнаўлення рэпазітара %s" + +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" -msgstr "Форки %s адлучаны" - -#: kallithea/controllers/admin/repos.py:313 +msgstr "Форкі %s адлучаныя" + +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" -msgstr "Выдалены форки рэпазітара %s" - -#: kallithea/controllers/admin/repos.py:318 +msgstr "Выдаленыя форки рэпазітара %s" + +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "Рэпазітар %s выдалены" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "Немагчыма выдаліць %s, ён усё яшчэ мае форкі" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" -msgstr "Адбылася памылка падчас выдалення %s" - -#: kallithea/controllers/admin/repos.py:374 +msgstr "Памылка падчас выдалення %s" + +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" -msgstr "Прывілеі рэпазітара абноўлены" - -#: kallithea/controllers/admin/repos.py:430 +msgstr "Прывілеі рэпазітара абноўленыя" + +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" -msgstr "Адбылася памылка пры стварэнні поля" - -#: kallithea/controllers/admin/repos.py:444 +msgstr "Памылка пры стварэнні поля" + +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" -msgstr "Адбылася памылка пры выдаленні поля" - -#: kallithea/controllers/admin/repos.py:460 +msgstr "Памылка пры выдаленні поля" + +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "-- Не форк --" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "Бачнасць рэпазітара ў публічным часопісе абноўлена" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" -msgstr "Адбылася памылка пры ўсталёўцы рэпазітара ў агульнадаступны часопіс" - -#: kallithea/controllers/admin/repos.py:512 +msgstr "Памылка пры даданні рэпазітара ў агульнадаступны часопіс" + +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "Нічога" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "Рэпазітар %s адзначаны як форк %s" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" -msgstr "Адбылася памылка пры выкананні аперацыі" - -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +msgstr "Памылка пры выкананні аперацыі" + +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 msgid "Repository has been locked" msgstr "Рэпазітар заблакаваны" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 msgid "Repository has been unlocked" -msgstr "Рэпазітар адблакаваны" - -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +msgstr "Рэпазітар разблакаваны" + +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" -msgstr "Адбылася памылка падчас разблакавання" - -#: kallithea/controllers/admin/repos.py:582 +msgstr "Памылка падчас разблакавання" + +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "Кэш скінуты" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" -msgstr "Адбылася памылка пры ачыстцы кэша" - -#: kallithea/controllers/admin/repos.py:601 +msgstr "Памылка пры скіданні кэша" + +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" -msgstr "Занесены змены з выдаленага рэпазітара" - -#: kallithea/controllers/admin/repos.py:604 +msgstr "Занесеныя змены з аддаленага рэпазітара" + +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" -msgstr "Адбылася памылка пры занясенні змен з выдаленага рэпазітара" - -#: kallithea/controllers/admin/repos.py:637 +msgstr "Памылка пры занясенні змен з аддаленага рэпазітара" + +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "Адбылася памылка пры выдаленні статыстыкі рэпазітара" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "Абноўлены налады VCS" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -"Немагчыма ўключыць падтрымку hgsubversion. Бібліятэка «hgsubversion» " +"Немагчыма ўключыць падтрымку hgsubversion. Бібліятэка hgsubversion " "адсутнічае" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" -msgstr "Адбылася памылка пры абнаўленні налад прыкладання" - -#: kallithea/controllers/admin/settings.py:216 +msgstr "Памылка пры абнаўленні наладаў праграмы" + +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." -msgstr "Рэпазітары паспяхова перасканіраваны, дададзена: %s, выдалена: %s." - -#: kallithea/controllers/admin/settings.py:273 +msgstr "Рэпазітары паспяхова перасканаваныя, дададзена: %s, выдалена: %s." + +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" -msgstr "Абноўленыя параметры налады прыкладання" - -#: kallithea/controllers/admin/settings.py:330 +msgstr "Абноўленыя налады праграмы" + +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "Налады візуалізацыі абноўленыя" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" -msgstr "Адбылася памылка пры абнаўленні налад візуалізацыі" - -#: kallithea/controllers/admin/settings.py:361 +msgstr "Адбылася памылка пры абнаўленні наладаў візуалізацыі" + +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" -msgstr "Калі ласка, увядзіце email-адрас" - -#: kallithea/controllers/admin/settings.py:376 +msgstr "Калі ласка, увядзіце e-mail-адрас" + +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "Задача адпраўкі e-mail створаная" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "Дададзены новы хук" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "Абноўленыя хукі" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" -msgstr "адбылася памылка пры стварэнні хука" - -#: kallithea/controllers/admin/settings.py:451 +msgstr "Памылка пры стварэнні хука" + +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" -msgstr "Запланавана пераіндэксаванне базы Whoosh" +msgstr "Запланаванае пераіндэксаванне базы Whoosh" #: kallithea/controllers/admin/user_groups.py:150 #, python-format msgid "Created user group %s" -msgstr "Створана група карыстачоў %s" +msgstr "Створана група карыстальнікаў %s" #: kallithea/controllers/admin/user_groups.py:163 #, python-format msgid "Error occurred during creation of user group %s" -msgstr "Адбылася памылка пры стварэнні групы карыстачоў %s" +msgstr "Памылка пры стварэнні групы карыстальнікаў %s" #: kallithea/controllers/admin/user_groups.py:201 #, python-format msgid "Updated user group %s" -msgstr "Група карыстачоў %s абноўлена" +msgstr "Група карыстальнікаў %s абноўленая" #: kallithea/controllers/admin/user_groups.py:224 #, python-format msgid "Error occurred during update of user group %s" -msgstr "Адбылася памылка пры абнаўленні групы карыстачоў %s" +msgstr "Памылка пры абнаўленні групы карыстальнікаў %s" #: kallithea/controllers/admin/user_groups.py:242 msgid "Successfully deleted user group" -msgstr "Група карыстачоў паспяхова выдалена" +msgstr "Група карыстальнікаў паспяхова выдаленая" #: kallithea/controllers/admin/user_groups.py:247 msgid "An error occurred during deletion of user group" -msgstr "Адбылася памылка пры выдаленні групы карыстачоў" +msgstr "Памылка пры выдаленні групы карыстальнікаў" #: kallithea/controllers/admin/user_groups.py:314 msgid "Target group cannot be the same" -msgstr "Мэтавая група не можа быць такі ж" +msgstr "Мэтавая група не можа быць той жа самай" #: kallithea/controllers/admin/user_groups.py:320 msgid "User group permissions updated" -msgstr "Прывілеі групы карыстачоў абноўлены" +msgstr "Прывілеі групы карыстальнікаў абноўленыя" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" -msgstr "Абноўлены прывілеі" +msgstr "Абноўленыя прывілеі" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" -msgstr "Адбылася памылка пры захаванні прывілеяў" - -#: kallithea/controllers/admin/users.py:134 +msgstr "Памылка пры захаванні прывілеяў" + +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" -msgstr "Карыстач %s створаны" - -#: kallithea/controllers/admin/users.py:149 +msgstr "Карыстальнік %s створаны" + +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" -msgstr "Адбылася памылка пры стварэнні карыстача %s" - -#: kallithea/controllers/admin/users.py:182 +msgstr "Памылка пры стварэнні карыстальніка %s" + +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" -msgstr "Карыстач паспяхова абноўлены" - -#: kallithea/controllers/admin/users.py:218 +msgstr "Карыстальнік паспяхова абноўлены" + +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" -msgstr "Карыстач паспяхова выдалены" - -#: kallithea/controllers/admin/users.py:223 +msgstr "Карыстальнік паспяхова выдалены" + +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" -msgstr "Адбылася памылка пры выдаленні карыстача" - -#: kallithea/controllers/admin/users.py:236 +msgstr "Памылка пры выдаленні карыстальніка" + +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" -msgstr "Дададзены IP %s у белы спіс карыстача" - -#: kallithea/controllers/admin/users.py:469 +msgstr "Дададзены IP %s у белы спіс карыстальніка" + +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "Адбылася памылка пры захаванні IP" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" -msgstr "Выдалены IP %s з белага спісу карыстача" - -#: kallithea/lib/auth.py:743 +msgstr "Выдалены IP %s з белага спісу карыстальніка" + +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "IP %s заблакаваны" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "Няслушны API-ключ" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" -msgstr "Вы павінны быць зарэгістраваным карыстачом, каб выканаць гэта дзеянне" - -#: kallithea/lib/auth.py:844 +msgstr "Вы павінны быць зарэгістраваным карыстальнікам, каб выканаць гэта дзеянне" + +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" -msgstr "Старонка даступная толькі аўтарызаваным карыстачам" - -#: kallithea/lib/base.py:490 +msgstr "Старонка даступная толькі аўтарызаваным карыстальнікам" + +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "Рэпазітар не знойдзены на файлавай сістэме" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "Набор змен не знойдзены" @@ -1075,126 +1080,125 @@ msgid "No changes detected" msgstr "Змен не выяўлена" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" -msgstr "Выдалена галінка: %s" - -#: kallithea/lib/helpers.py:611 +msgstr "Выдаленая галіна: %s" + +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "Створаны тэг: %s" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "Паказаць адрозненні разам %s->%s" -#: kallithea/lib/helpers.py:677 -#, fuzzy +#: kallithea/lib/helpers.py:678 msgid "Compare view" -msgstr "параўнанне" - -#: kallithea/lib/helpers.py:696 +msgstr "Параўнанне" + +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "і" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "на %s больш" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "версіі" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "Імя форка %s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "Pull-запыт %s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "[выдалены] рэпазітар" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "[створаны] рэпазітар" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "[створаны] рэпазітар як форк" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "[форкнуты] рэпазітар" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "[абноўлены] рэпазітар" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "[загружаны] архіў з рэпазітара" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "[выдалены] рэпазітар" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" -msgstr "[створаны] карыстач" - -#: kallithea/lib/helpers.py:774 +msgstr "[створаны] карыстальнік" + +#: kallithea/lib/helpers.py:775 msgid "[updated] user" -msgstr "[абноўлены] карыстач" - -#: kallithea/lib/helpers.py:776 +msgstr "[абноўлены] карыстальнік" + +#: kallithea/lib/helpers.py:777 msgid "[created] user group" -msgstr "[створана] група карыстачоў" - -#: kallithea/lib/helpers.py:778 +msgstr "[створана] група карыстальнікаў" + +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" -msgstr "[абноўлена] група карыстачоў" - -#: kallithea/lib/helpers.py:780 +msgstr "[абноўлена] група карыстальнікаў" + +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "[каментар] да рэвізіі ў рэпазітары" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" -msgstr "[пракаменціравана] у запыце на занясенне змен для" - -#: kallithea/lib/helpers.py:784 +msgstr "[каментар] у pull-запыце для" + +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" -msgstr "[зачынены] Pull-запыт для" - -#: kallithea/lib/helpers.py:786 +msgstr "[зачынены] pull-запыт для" + +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[адпраўлена] у" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" -msgstr "[занесены змены з дапамогай Kallithea] у рэпазітары" - -#: kallithea/lib/helpers.py:790 +msgstr "[каміт праз Kallithea] у рэпазітары" + +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" -msgstr "[занесены змены з выдаленага рэпазітара] у рэпазітар" - -#: kallithea/lib/helpers.py:792 +msgstr "[занесены з аддаленага рэпазітара] у рэпазітар" + +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" -msgstr "[занесены змены] з" - -#: kallithea/lib/helpers.py:794 +msgstr "[занесены] з" + +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "[дададзены ў назіранні] рэпазітар" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "[выдалены з назірання] рэпазітар" @@ -1204,8 +1208,8 @@ msgstr " і на %s больш" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "Няма файлаў" @@ -1229,7 +1233,7 @@ msgid "chmod" msgstr "chmod" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1240,39 +1244,39 @@ "пераназваны з файлавай сістэмы. Калі ласка, перазапусціце прыкладанне для" " сканавання рэпазітароў" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "%d год" -msgstr[1] "%d гадоў" -msgstr[2] "%d гады" - -#: kallithea/lib/utils2.py:416 +msgstr[1] "%d гады" +msgstr[2] "%d гадоў" + +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d месяц" -msgstr[1] "%d месяца" +msgstr[1] "%d месяцы" msgstr[2] "%d месяцаў" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d дзень" -msgstr[1] "%d дня" +msgstr[1] "%d дні" msgstr[2] "%d дзён" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d гадзіна" -msgstr[1] "%d гадзін" -msgstr[2] "%d гадзіны" - -#: kallithea/lib/utils2.py:419 +msgstr[1] "%d гадзіны" +msgstr[2] "%d гадзін" + +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" @@ -1280,37 +1284,37 @@ msgstr[1] "%d хвіліны" msgstr[2] "%d хвілін" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" -msgstr[0] "%d секунды" +msgstr[0] "%d секунда" msgstr[1] "%d секунды" -msgstr[2] "%d секунды" - -#: kallithea/lib/utils2.py:436 +msgstr[2] "%d секунд" + +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "у %s" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "%s назад" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "у %s і %s" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "%s і %s назад" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" -msgstr "прама цяпер" +msgstr "цяпер" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182 @@ -1407,7 +1411,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Адміністратар Kallithea" @@ -1424,7 +1428,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670 msgid "Repository creation disabled" -msgstr "Стварэнне рэпазітароў адключана" +msgstr "Стварэнне рэпазітароў адключанае" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194 @@ -1439,7 +1443,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671 msgid "Repository creation enabled" -msgstr "Стварэнне рэпазітароў уключана" +msgstr "Стварэнне рэпазітароў уключанае" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195 @@ -1454,7 +1458,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675 msgid "Repository forking disabled" -msgstr "Магчымасць ствараць форк рэпазітара адключана" +msgstr "Магчымасць ствараць форк рэпазітара адключаная" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196 @@ -1469,28 +1473,28 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676 msgid "Repository forking enabled" -msgstr "Магчымасць ствараць форк рэпазітара ўключана" +msgstr "Магчымасць ствараць форк рэпазітара ўключаная" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197 #: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318 #: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403 msgid "Register disabled" -msgstr "Рэгістрацыя адключана" +msgstr "Рэгістрацыя адключаная" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198 #: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319 #: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404 msgid "Register new user with Kallithea with manual activation" -msgstr "Рэгістрацыя новага карыстача ў Kallithea з ручной актывацыяй" +msgstr "Рэгістрацыя новага карыстальніка ў Kallithea з ручной актывацыяй" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201 #: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322 #: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407 msgid "Register new user with Kallithea with auto activation" -msgstr "Рэгістрацыя новага карыстача ў Kallithea з аўтаматычнай актывацыяй" +msgstr "Рэгістрацыя новага карыстальніка ў Kallithea з аўтаматычнай актывацыяй" #: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623 #: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650 @@ -1518,7 +1522,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "Ухвалена" @@ -1533,7 +1537,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "Адхілена" @@ -1560,7 +1564,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "верхні ўзровень" @@ -1621,7 +1625,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659 msgid "User group no access" -msgstr "Група карыстачоў - няма доступу" +msgstr "Група карыстальнікаў - няма доступу" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465 @@ -1632,7 +1636,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660 msgid "User group read access" -msgstr "Група карыстачоў - доступ на чытанне" +msgstr "Група карыстальнікаў - доступ на чытанне" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466 @@ -1643,7 +1647,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661 msgid "User group write access" -msgstr "Група карыстачоў - доступ на запіс" +msgstr "Група карыстальнікаў - доступ на запіс" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467 @@ -1654,7 +1658,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662 msgid "User group admin access" -msgstr "Група карыстачоў - адміністраванне" +msgstr "Група карыстальнікаў - адміністраванне" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469 @@ -1665,7 +1669,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664 msgid "Repository Group creation disabled" -msgstr "Стварэнне груп рэпазітароў адключана" +msgstr "Стварэнне груп рэпазітароў адключанае" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470 @@ -1676,7 +1680,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665 msgid "Repository Group creation enabled" -msgstr "Стварэнне груп рэпазітароў уключана" +msgstr "Стварэнне груп рэпазітароў уключанае" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472 @@ -1687,7 +1691,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667 msgid "User Group creation disabled" -msgstr "Стварэнне груп карыстачоў адключана" +msgstr "Стварэнне груп карыстальнікаў адключанае" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473 @@ -1698,7 +1702,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668 msgid "User Group creation enabled" -msgstr "Стварэнне груп карыстачоў уключана" +msgstr "Стварэнне груп карыстальнікаў уключанае" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481 @@ -1707,9 +1711,9 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" -msgstr "Рэгістрацыя адключана" +msgstr "Рэгістрацыя адключаная" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482 @@ -1720,7 +1724,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679 msgid "User Registration with manual account activation" -msgstr "Рэгістрацыя карыстача з ручной актывацыяй уліковага запісу" +msgstr "Рэгістрацыя карыстальніка з ручной актывацыяй уліковага запісу" #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437 #: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483 @@ -1731,15 +1735,15 @@ #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653 #: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680 msgid "User Registration with automatic account activation" -msgstr "Рэгістрацыя карыстача з аўтаматычнай актывацыяй" +msgstr "Рэгістрацыя карыстальніка з аўтаматычнай актывацыяй" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1748,117 +1752,103 @@ msgid "on line %s" msgstr "на радку %s" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[Згадванне]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 -#, fuzzy -msgid "Default user has read access to new repositories" -msgstr "Несанкцыянаваны доступ да рэсурсу" - -#: kallithea/model/db.py:1669 -#, fuzzy -msgid "Default user has write access to new repositories" -msgstr "Несанкцыянаваны доступ да рэсурсу" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" +msgid "Default user has read access to new repositories" msgstr "" #: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" +msgid "Default user has write access to new repositories" msgstr "" #: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" msgstr "" #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -#, fuzzy -msgid "Only admins can create repository groups" -msgstr "Створана новая група рэпазітароў %s" +msgid "Default user has read access to new user groups" +msgstr "" #: kallithea/model/db.py:1683 -#, fuzzy -msgid "Non-admins can create repository groups" -msgstr "Створана новая група рэпазітароў %s" - -#: kallithea/model/db.py:1685 -#, fuzzy -msgid "Only admins can create user groups" -msgstr "Ствараць групы карыстачоў" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" +msgstr "" #: kallithea/model/db.py:1686 -#, fuzzy -msgid "Non-admins can create user groups" -msgstr "Ствараць групы карыстачоў" - -#: kallithea/model/db.py:1688 -#, fuzzy -msgid "Only admins can create top level repositories" -msgstr "Рэпазітары верхняга ўзроўню" +msgid "Only admins can create repository groups" +msgstr "Толькі адміністратары могуць ствараць групы репазітароў" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" +msgstr "Неадміністратары могуць ствараць групы репазітароў" #: kallithea/model/db.py:1689 -#, fuzzy +msgid "Only admins can create user groups" +msgstr "Толькі адміністратары могуць ствараць групы карыстальнікаў" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "Неадміністратары могуць ствараць групы карыстальнікаў" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "Толькі адміністратары могуць ствараць рэпазітары верхняга ўзроўню" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" -msgstr "Рэпазітары верхняга ўзроўню" - -#: kallithea/model/db.py:1694 -#, fuzzy -msgid "Only admins can fork repositories" -msgstr "Месцазнаходжанне рэпазітароў" - -#: kallithea/model/db.py:1695 -#, fuzzy -msgid "Non-admins can can fork repositories" -msgstr "Скінуць кэш для ўсіх рэпазітароў" +msgstr "Неадміністратары могуць ствараць рэпазітары верхняга ўзроўню" #: kallithea/model/db.py:1698 -#, fuzzy -msgid "User registration with manual account activation" -msgstr "Рэгістрацыя карыстача з ручной актывацыяй уліковага запісу" +msgid "Only admins can fork repositories" +msgstr "Месцазнаходжанне рэпазітароў" #: kallithea/model/db.py:1699 -#, fuzzy +msgid "Non-admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1702 +msgid "User registration with manual account activation" +msgstr "Рэгістрацыя карыстальніка з ручной актывацыяй уліковага запісу" + +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" -msgstr "Рэгістрацыя карыстача з аўтаматычнай актывацыяй" - -#: kallithea/model/db.py:2228 -#, fuzzy +msgstr "Рэгістрацыя карыстальніка з аўтаматычнай актывацыяй" + +#: kallithea/model/db.py:2236 msgid "Not reviewed" msgstr "Не прагледжана" -#: kallithea/model/db.py:2231 -#, fuzzy +#: kallithea/model/db.py:2239 msgid "Under review" msgstr "На разглядзе" @@ -1880,7 +1870,7 @@ msgid "Enter %(min)i characters or more" msgstr "Увядзіце не меней %(min)i знакаў" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "Імя не можа ўтрымліваць толькі лічбы" @@ -1952,20 +1942,17 @@ #: kallithea/model/notification.py:305 #, python-format msgid "New user %(new_username)s registered" -msgstr "Новы карыстач \"%(new_username)s\" зарэгістраваны" +msgstr "Новы карыстальнік \"%(new_username)s\" зарэгістраваны" #: kallithea/model/notification.py:307 #, fuzzy, python-format -#| msgid "%(user)s wants you to review pull request %(pr_nice_id)s: -#| %(pr_title)s" msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "%(user)s просіць вас разгледзець pull request %(pr_nice_id)s: %(pr_title)s" #: kallithea/model/notification.py:308 #, fuzzy, python-format -#| msgid "[commented] on pull request for" msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" -msgstr "[пракаменціравана] у запыце на занясенне змен для" +msgstr "[пракаментавана] у запыце на занясенне змен для" #: kallithea/model/notification.py:321 msgid "Closing" @@ -1976,19 +1963,19 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "%(user)s просіць вас разгледзець pull request %(pr_nice_id)s: %(pr_title)s" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "апошняя версія" #: kallithea/model/user.py:192 msgid "New user registration" -msgstr "Рэгістрацыя новага карыстача" +msgstr "Рэгістрацыя новага карыстальніка" #: kallithea/model/user.py:256 msgid "You can't remove this user since it is crucial for the entire application" msgstr "" -"Вы не можаце выдаліць карыстача, паколькі гэта крытычна для працы ўсяго " -"прыкладання" +"Вы не можаце выдаліць карыстальніка, паколькі гэта крытычна для працы " +"ўсёй праграмы" #: kallithea/model/user.py:261 #, python-format @@ -1996,8 +1983,9 @@ "User \"%s\" still owns %s repositories and cannot be removed. Switch " "owners or remove those repositories: %s" msgstr "" -"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s рэпазітароў і таму не" -" можа быць выдалены. Зменіце ўладальніка ці выдаліце гэтыя рэпазітары: %s" +"Карыстальнік \"%s\" усё яшчэ з'яўляецца ўладальнікам %s рэпазітароў і " +"таму не можа быць выдалены. Змяніце ўладальніка ці выдаліце гэтыя " +"рэпазітары: %s" #: kallithea/model/user.py:266 #, python-format @@ -2005,8 +1993,8 @@ "User \"%s\" still owns %s repository groups and cannot be removed. Switch" " owners or remove those repository groups: %s" msgstr "" -"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп рэпазітароў і " -"таму не можа быць выдалены. Зменіце ўладальніка ці выдаліце дадзеныя " +"Карыстальнік \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп рэпазітароў " +"і таму не можа быць выдалены. Змяніце ўладальніка ці выдаліце гэтая " "групы: %s" #: kallithea/model/user.py:273 @@ -2015,21 +2003,19 @@ "User \"%s\" still owns %s user groups and cannot be removed. Switch " "owners or remove those user groups: %s" msgstr "" -"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп карыстачоў і " -"таму не можа быць выдалены. Зменіце ўладальніка ці выдаліце дадзеныя " -"групы: %s" - -#: kallithea/model/user.py:360 +"Карыстальнік \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп " +"карыстальнікаў і таму не можа быць выдалены. Змяніце ўладальніка ці " +"выдаліце гэтыя групы: %s" + +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "Спасылка скіду пароля" -#: kallithea/model/user.py:408 -#, fuzzy -#| msgid "Password reset link" +#: kallithea/model/user.py:418 msgid "Password reset notification" -msgstr "Спасылка скіду пароля" - -#: kallithea/model/user.py:409 +msgstr "Паведамленне пра скіданне пароля" + +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -2040,112 +2026,111 @@ msgid "Value cannot be an empty list" msgstr "Значэнне не можа быць пустым спісам" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" -msgstr "Карыстач з імем \"%(username)s\" ужо існуе" - -#: kallithea/model/validators.py:97 +msgstr "Карыстальнік з імем \"%(username)s\" ужо існуе" + +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "Імя \"%(username)s\" недапушчальнае" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -"Імя карыстача можа ўтрымоўваць толькі літары, лічбы, знакі падкрэслення, " -"кропкі і працяжнік; а гэтак жа павінна пачынацца з літары, лічбы або са " -"знака падкрэслення" - -#: kallithea/model/validators.py:126 +"Імя карыстальніка можа ўтрымоўваць толькі літары, лічбы, знакі " +"падкрэслення, кропкі і працяжнік; а гэтак жа павінна пачынацца з літары, " +"лічбы або са знака падкрэслення" + +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" -msgstr "Імя \"%(username)s\" недапушчальна" - -#: kallithea/model/validators.py:152 +msgstr "Імя \"%(username)s\" недапушчальнае" + +#: kallithea/model/validators.py:154 msgid "Invalid user group name" -msgstr "Няслушнае імя групы карыстачоў" - -#: kallithea/model/validators.py:153 +msgstr "Няслушнае імя групы карыстальнікаў" + +#: kallithea/model/validators.py:155 #, python-format msgid "User group \"%(usergroup)s\" already exists" -msgstr "Група карыстачоў \"%(usergroup)s\" ужо існуе" - -#: kallithea/model/validators.py:155 +msgstr "Група карыстальнікаў \"%(usergroup)s\" ужо існуе" + +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" msgstr "" -"імя групы карыстачоў можа ўтрымоўваць толькі літары, лічбы, знакі " +"імя групы карыстальнікаў можа ўтрымоўваць толькі літары, лічбы, знакі " "падкрэслення, кропкі і працяжнік; а гэтак жа павінна пачынацца з літары " "ці лічбы" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "Немагчыма выкарыстоўваць гэту групу як бацькоўскую" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "Група \"%(group_name)s\" ужо існуе" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "Рэпазітар з імем \"%(group_name)s\" ужо існуе" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "Недапушчальныя знакі (не ascii) у паролі" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "Няслушна зададзены стары пароль" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "Паролі не супадаюць" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "Няслушнае імя ці пароль" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "Несупадзенне токенаў" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "Імя рэпазітара %(repo)s забароненае" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "Рэпазітар %(repo)s ужо існуе" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "Рэпазітар \"%(repo)s\" ужо існуе ў групе \"%(group)s\"" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "Група рэпазітароў \"%(repo)s\" ужо існуе" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 msgid "Invalid repository URL" msgstr "Няслушны URL рэпазітара" -#: kallithea/model/validators.py:466 -#, fuzzy +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" @@ -2153,69 +2138,69 @@ "Няслушны URL рэпазітара. Ён мусіць быць карэктным URL http, https, ssh, " "svn+http ці svn+https" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "Тып форка будзе супадаць з бацькоўскім" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" -msgstr "У вас недастаткова мае рацыю для стварэння рэпазітароў у гэтай групе" - -#: kallithea/model/validators.py:506 +msgstr "У вас недастаткова правоў для стварэння рэпазітароў у гэтай групе" + +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" -msgstr "недастаткова мае рацыю для стварэння рэпазітара ў каранёвым каталогу" - -#: kallithea/model/validators.py:556 +msgstr "недастаткова правоў для стварэння рэпазітара ў каранёвым каталогу" + +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "У Вас недастаткова прывілеяў для стварэння групы ў гэтым месцы" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" -msgstr "Дадзенае імя карыстача ці групы карыстачоў недапушчальна" - -#: kallithea/model/validators.py:690 +msgstr "Дадзенае імя карыстальніка ці групы карыстальнікаў недапушчальна" + +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "Гэты шлях хібны" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" -msgstr "Гэты E-mail ужо заняты" - -#: kallithea/model/validators.py:725 -#, fuzzy, python-format +msgstr "Гэты e-mail ужо ўжываецца" + +#: kallithea/model/validators.py:734 +#, python-format msgid "Email address \"%(email)s\" not found" -msgstr "\"%(email)s\" не існуе." - -#: kallithea/model/validators.py:762 +msgstr "e-mail \"%(email)s\" не існуе." + +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "" "Для ўваходу па LDAP павінна быць паказана значэнне атрыбута CN - гэта " -"эквівалент імя карыстача" - -#: kallithea/model/validators.py:774 +"эквівалент імя карыстальніка" + +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "Калі ласка, увядзіце існы IPv4 ці IPv6 адрас" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" "Значэнне маскі падсеткі павінна быць у межах ад 0 да 32 (%(bits)r - " "няслушна)" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" "Ключавое імя можа толькі складацца з літар, знака падкрэслення, працяжнік" " ці лікаў" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "Файла няма ў каталогу" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2331,7 +2316,7 @@ #: kallithea/templates/journal/journal.html:189 #: kallithea/templates/journal/journal.html:280 msgid "Tip" -msgstr "Стан" +msgstr "" #: kallithea/templates/index_base.html:132 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10 @@ -2342,7 +2327,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "Уладальнік" @@ -2373,7 +2358,7 @@ #: kallithea/templates/index_base.html:142 msgid "No repositories found." -msgstr "Рэпазітары не знойдзены." +msgstr "Рэпазітары не знойдзеныя." #: kallithea/templates/index_base.html:143 #: kallithea/templates/admin/my_account/my_account_repos.html:60 @@ -2390,7 +2375,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2400,7 +2385,7 @@ msgstr "Загрузка..." #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "Увайсці" @@ -2415,15 +2400,15 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" -msgstr "Імя карыстача" +msgstr "Імя карыстальніка" #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "Пароль" @@ -2433,9 +2418,9 @@ #: kallithea/templates/login.html:53 msgid "Forgot your password ?" -msgstr "Забыліся пароль?" - -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +msgstr "Забыліся на пароль?" + +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "Няма акаўнта?" @@ -2445,13 +2430,13 @@ #: kallithea/templates/password_reset.html:5 msgid "Password Reset" -msgstr "Скід пароля" +msgstr "Скінуць пароль" #: kallithea/templates/password_reset.html:12 #: kallithea/templates/password_reset_confirmation.html:12 #, python-format msgid "Reset Your Password to %s" -msgstr "Забыліся пароль для %s?" +msgstr "Забыліся на пароль для %s?" #: kallithea/templates/password_reset.html:14 #: kallithea/templates/password_reset_confirmation.html:5 @@ -2470,16 +2455,14 @@ #: kallithea/templates/password_reset.html:46 msgid "Send Password Reset Email" -msgstr "Паслаць спасылку скіду пароля" +msgstr "Паслаць спасылку для скідання пароля" #: kallithea/templates/password_reset.html:47 #, fuzzy -#| msgid "" "Password reset link will be sent to the email address matching -#| your " "username." msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." -msgstr "Спасылка для скіду пароля была адпраўлена на адпаведны e-mail." +msgstr "Спасылка для скіду пароля будзе адпраўленая на адпаведны e-mail." #: kallithea/templates/password_reset_confirmation.html:19 #, python-format @@ -2497,14 +2480,10 @@ msgstr "" #: kallithea/templates/password_reset_confirmation.html:39 -#, fuzzy -#| msgid "New password" msgid "New Password" msgstr "Новы пароль" #: kallithea/templates/password_reset_confirmation.html:48 -#, fuzzy -#| msgid "Confirm new password" msgid "Confirm New Password" msgstr "Пацвердзіце новы пароль" @@ -2520,11 +2499,11 @@ #: kallithea/templates/register.html:12 #, python-format msgid "Sign Up to %s" -msgstr "Рэгістра на %s" +msgstr "Рэгістрацыя на %s" #: kallithea/templates/register.html:42 msgid "Re-enter password" -msgstr "Паўторыце пароль" +msgstr "Паўтарыце пароль" #: kallithea/templates/register.html:51 #: kallithea/templates/admin/my_account/my_account_profile.html:34 @@ -2561,11 +2540,7 @@ #: kallithea/templates/switch_to_list.html:10 #: kallithea/templates/branches/branches_data.html:69 msgid "There are no branches yet" -msgstr "Галінкі яшчэ не створаны" - -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "Зачыненыя галінкі" +msgstr "Галіны яшчэ не створаныя" #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 @@ -2581,11 +2556,11 @@ #: kallithea/templates/admin/admin.html:13 #: kallithea/templates/base/base.html:59 msgid "Admin Journal" -msgstr "Часопіс адміністратара" +msgstr "Журнал адміністратара" #: kallithea/templates/admin/admin.html:10 msgid "journal filter..." -msgstr "Фільтр часопіса..." +msgstr "Фільтр журнала..." #: kallithea/templates/admin/admin.html:12 #: kallithea/templates/journal/journal.html:11 @@ -2635,7 +2610,7 @@ #: kallithea/templates/admin/admin_log.html:63 msgid "No actions yet" -msgstr "Дзеянні яшчэ не вырабляліся" +msgstr "Няма інфармацыі" #: kallithea/templates/admin/auth/auth_settings.html:5 msgid "Authentication Settings" @@ -2648,11 +2623,11 @@ #: kallithea/templates/admin/auth/auth_settings.html:28 msgid "Authentication Plugins" -msgstr "Убудовы аўтэнтыфікацыі" +msgstr "Плагіны аўтэнтыфікацыі" #: kallithea/templates/admin/auth/auth_settings.html:31 msgid "Enabled Plugins" -msgstr "Уключаныя ўбудовы" +msgstr "Уключаныя плагіны" #: kallithea/templates/admin/auth/auth_settings.html:33 #, fuzzy @@ -2660,16 +2635,16 @@ "Comma-separated list of plugins; Kallithea will try user authentication " "in plugin order" msgstr "" -"Спіс убудоў, падзеленых коскі. Kallithea будзе спрабаваць аўтэнтыфікаваць" -" карыстача ў парадку ўказання ўбудоў" +"Спіс плагінаў, падзеленых коскамі. Kallithea будзе спрабаваць " +"аўтэнтыфікаваць карыстальніка ў парадку ўказання плагінаў" #: kallithea/templates/admin/auth/auth_settings.html:34 msgid "Available built-in plugins" -msgstr "Даступныя ўбудаваныя ўбудовы" +msgstr "Даступныя ўбудаваныя плагіны" #: kallithea/templates/admin/auth/auth_settings.html:51 msgid "Plugin" -msgstr "Убудова" +msgstr "Плагін" #: kallithea/templates/admin/auth/auth_settings.html:101 #: kallithea/templates/admin/defaults/defaults.html:82 @@ -2695,7 +2670,7 @@ #: kallithea/templates/admin/defaults/defaults.html:11 #: kallithea/templates/base/base.html:66 msgid "Repository Defaults" -msgstr "Значэнні па змаўчанні" +msgstr "Значэнні па змоўчанні" #: kallithea/templates/admin/defaults/defaults.html:33 #: kallithea/templates/admin/repos/repo_add_base.html:55 @@ -2748,12 +2723,12 @@ #: kallithea/templates/admin/defaults/defaults.html:77 #: kallithea/templates/admin/repos/repo_edit_settings.html:106 msgid "Enable lock-by-pulling on repository." -msgstr "Уключыць аўтаблакоўку для рэпазітара." +msgstr "Уключыць аўтаблакаванне для рэпазітара." #: kallithea/templates/admin/gists/edit.html:5 #: kallithea/templates/admin/gists/edit.html:18 msgid "Edit Gist" -msgstr "Праўка gist-запісы" +msgstr "Правіць gist-запіс" #: kallithea/templates/admin/gists/edit.html:36 #, python-format @@ -2785,7 +2760,7 @@ #: kallithea/templates/admin/users/user_edit_api_keys.html:27 #: kallithea/templates/admin/users/user_edit_api_keys.html:32 msgid "Expires" -msgstr "Мінае" +msgstr "" #: kallithea/templates/admin/gists/edit.html:61 #: kallithea/templates/admin/gists/index.html:57 @@ -2794,16 +2769,15 @@ #: kallithea/templates/admin/my_account/my_account_api_keys.html:27 #: kallithea/templates/admin/users/user_edit_api_keys.html:8 #: kallithea/templates/admin/users/user_edit_api_keys.html:27 -#, fuzzy msgid "Never" -msgstr "ніколі" - -#: kallithea/templates/admin/gists/edit.html:145 +msgstr "Ніколі" + +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "Абнавіць" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "Адмена" @@ -2811,13 +2785,13 @@ #: kallithea/templates/admin/gists/index.html:16 #, python-format msgid "Private Gists for User %s" -msgstr "Прыватная gist-запіс для карыстача %s" +msgstr "Прыватны gist-запіс для карыстальніка %s" #: kallithea/templates/admin/gists/index.html:8 #: kallithea/templates/admin/gists/index.html:18 #, python-format msgid "Public Gists for User %s" -msgstr "Публічная gist-запіс для карыстача %s" +msgstr "Публічны gist-запіс для карыстальніка %s" #: kallithea/templates/admin/gists/index.html:10 #: kallithea/templates/admin/gists/index.html:20 @@ -2826,14 +2800,14 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" -msgstr "Стварыць новую gist-запіс" +msgstr "Стварыць новы gist-запіс" #: kallithea/templates/admin/gists/index.html:54 #: kallithea/templates/data_table/_dt_elements.html:141 msgid "Created" -msgstr "Створана" +msgstr "Створаны" #: kallithea/templates/admin/gists/index.html:74 msgid "There are no gists yet" @@ -2842,7 +2816,7 @@ #: kallithea/templates/admin/gists/new.html:5 #: kallithea/templates/admin/gists/new.html:18 msgid "New Gist" -msgstr "" +msgstr "Новы gist-запіс" #: kallithea/templates/admin/gists/new.html:47 msgid "name this file..." @@ -2884,7 +2858,7 @@ #: kallithea/templates/files/files_edit.html:68 #: kallithea/templates/pullrequests/pullrequest.html:89 msgid "Reset" -msgstr "Скід" +msgstr "Скінуць" #: kallithea/templates/admin/gists/show.html:5 #: kallithea/templates/admin/gists/show.html:9 @@ -2914,7 +2888,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2928,14 +2903,12 @@ #: kallithea/templates/admin/gists/show.html:56 msgid "Confirm to delete this Gist" -msgstr "Пацвердзіце выдаленне гэтай gist-запісы" +msgstr "Пацвердзіце выдаленне гэтага gist-запісу" #: kallithea/templates/admin/gists/show.html:63 #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2962,13 +2935,12 @@ msgstr "створана" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "Паказаць толькі тэкст" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "Мой Акаўнт" @@ -2978,9 +2950,8 @@ msgstr "Профіль" #: kallithea/templates/admin/my_account/my_account.html:36 -#, fuzzy msgid "Email Addresses" -msgstr "Новы E-mail" +msgstr "E-mail адрэсы" #: kallithea/templates/admin/my_account/my_account.html:38 #: kallithea/templates/admin/users/user_edit.html:31 @@ -2988,9 +2959,8 @@ msgstr "API-ключы" #: kallithea/templates/admin/my_account/my_account.html:39 -#, fuzzy msgid "Owned Repositories" -msgstr "рэпазітары" +msgstr "Мае рэпазітары" #: kallithea/templates/admin/my_account/my_account.html:40 #: kallithea/templates/journal/journal.html:53 @@ -3002,9 +2972,8 @@ #: kallithea/templates/admin/permissions/permissions.html:30 #: kallithea/templates/admin/user_groups/user_group_edit.html:32 #: kallithea/templates/admin/users/user_edit.html:34 -#, fuzzy msgid "Show Permissions" -msgstr "Скапіяваць прывілеі" +msgstr "Паказаць прывілеі" #: kallithea/templates/admin/my_account/my_account_api_keys.html:6 #: kallithea/templates/admin/users/user_edit_api_keys.html:6 @@ -3015,7 +2984,7 @@ #: kallithea/templates/admin/users/user_edit_api_keys.html:14 #, fuzzy, python-format msgid "Confirm to reset this API key: %s" -msgstr "Пацвердзіце скід гэтага API-ключа: %s" +msgstr "Пацвердзіце скіданне гэтага API-ключа: %s" #: kallithea/templates/admin/my_account/my_account_api_keys.html:30 #: kallithea/templates/admin/users/user_edit_api_keys.html:30 @@ -3025,15 +2994,14 @@ #: kallithea/templates/admin/my_account/my_account_api_keys.html:40 #: kallithea/templates/admin/users/user_edit_api_keys.html:40 -#, fuzzy, python-format +#, python-format msgid "Confirm to remove this API key: %s" msgstr "Пацвердзіце выдаленне гэтага API-ключа: %s" #: kallithea/templates/admin/my_account/my_account_api_keys.html:42 #: kallithea/templates/admin/users/user_edit_api_keys.html:42 -#, fuzzy msgid "Remove" -msgstr "выдаліць" +msgstr "Выдаліць" #: kallithea/templates/admin/my_account/my_account_api_keys.html:49 #: kallithea/templates/admin/users/user_edit_api_keys.html:49 @@ -3043,9 +3011,8 @@ #: kallithea/templates/admin/my_account/my_account_api_keys.html:61 #: kallithea/templates/admin/users/user_edit_api_keys.html:61 -#, fuzzy msgid "New API key" -msgstr "Ключ" +msgstr "Новы API-ключ" #: kallithea/templates/admin/my_account/my_account_api_keys.html:69 #: kallithea/templates/admin/my_account/my_account_emails.html:45 @@ -3067,7 +3034,7 @@ #: kallithea/templates/admin/users/user_edit_emails.html:20 #, python-format msgid "Confirm to delete this email: %s" -msgstr "Пацвердзіце выдаленне E-mail: %s" +msgstr "Пацвердзіце выдаленне e-mail: %s" #: kallithea/templates/admin/my_account/my_account_emails.html:26 #: kallithea/templates/admin/users/user_edit_emails.html:26 @@ -3077,7 +3044,7 @@ #: kallithea/templates/admin/my_account/my_account_emails.html:38 #: kallithea/templates/admin/users/user_edit_emails.html:38 msgid "New email address" -msgstr "Новы E-mail" +msgstr "Новы e-mail" #: kallithea/templates/admin/my_account/my_account_password.html:1 msgid "Change Your Account Password" @@ -3085,7 +3052,7 @@ #: kallithea/templates/admin/my_account/my_account_password.html:10 msgid "Current password" -msgstr "Бягучы пароль" +msgstr "Цяперашні пароль" #: kallithea/templates/admin/my_account/my_account_password.html:19 #: kallithea/templates/admin/users/user_edit_profile.html:60 @@ -3103,7 +3070,7 @@ #: kallithea/templates/admin/my_account/my_account_profile.html:11 msgid "Change your avatar at" -msgstr "Зменіце аватар праз сайт" +msgstr "Змяніць аватар можна праз" #: kallithea/templates/admin/my_account/my_account_profile.html:12 #: kallithea/templates/admin/users/user_edit_profile.html:9 @@ -3113,17 +3080,16 @@ #: kallithea/templates/admin/my_account/my_account_profile.html:14 #: kallithea/templates/admin/users/user_edit_profile.html:11 msgid "Avatars are disabled" -msgstr "Аватары адключаны" +msgstr "Аватары адключаныя" #: kallithea/templates/admin/my_account/my_account_profile.html:15 msgid "Missing email, please update your user email address." -msgstr "Не паказаны email. Калі ласка, абновіце ваш email." +msgstr "Няма email адрэсы, калі ласка, абнавіце ваш email." #: kallithea/templates/admin/my_account/my_account_profile.html:16 #: kallithea/templates/admin/users/user_edit_profile.html:15 -#, fuzzy msgid "Current IP" -msgstr "бягучы IP-адрас" +msgstr "Цяперашні IP-адрас" #: kallithea/templates/admin/my_account/my_account_repos.html:1 msgid "Repositories You Own" @@ -3138,7 +3104,7 @@ #: kallithea/templates/journal/journal.html:291 #: kallithea/templates/tags/tags.html:81 msgid "No records found." -msgstr "Запісы не знойдзены." +msgstr "Запісы не знойдзеныя." #: kallithea/templates/admin/my_account/my_account_watched.html:1 msgid "Repositories You are Watching" @@ -3151,20 +3117,20 @@ #: kallithea/templates/admin/notifications/notifications.html:24 msgid "All" -msgstr "Усё" +msgstr "Усе" #: kallithea/templates/admin/notifications/notifications.html:25 msgid "Comments" msgstr "Каментары" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "Pull-запыты" #: kallithea/templates/admin/notifications/notifications.html:30 msgid "Mark All Read" -msgstr "Адзначыць усё як прачытаныя" +msgstr "Адзначыць усе як прачытаныя" #: kallithea/templates/admin/notifications/notifications_data.html:40 msgid "No notifications here yet" @@ -3176,7 +3142,7 @@ msgstr "Паказаць апавяшчэнне" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "Апавяшчэнні" @@ -3213,13 +3179,12 @@ "permission, note that all custom default permission on repositories will " "be lost" msgstr "" -"Абраныя прывілеі будуць усталяваны па змаўчанні для кожнага рэпазітара. " -"Улічыце, што раней усталяваныя прывілеі па змаўчанні будуць скінуты" +"Выбраныя прывілеі будуць усталяваныя па змоўчанні для кожнага рэпазітара." +" Улічыце, што раней усталяваныя прывілеі па змоўчанні будуць скінутыя" #: kallithea/templates/admin/permissions/permissions_globals.html:26 -#, fuzzy msgid "Apply to all existing repositories" -msgstr "Імпартаваць існы рэпазітар?" +msgstr "" #: kallithea/templates/admin/permissions/permissions_globals.html:27 msgid "Permissions for the Default user on new repositories." @@ -3239,14 +3204,13 @@ "permission, note that all custom default permission on repository groups " "will be lost" msgstr "" -"Абраныя прывілеі будуць усталяваны па змаўчанні для кожнай групы " -"рэпазітароў. Улічыце, што раней усталяваныя прывілеі па змаўчанні для " -"груп рэпазітароў будуць скінуты" +"Выбраныя прывілеі будуць усталяваныя па змоўчанні для кожнай групы " +"рэпазітароў. Улічыце, што раней усталяваныя прывілеі па змоўчанні для " +"груп рэпазітароў будуць скінутыя" #: kallithea/templates/admin/permissions/permissions_globals.html:40 -#, fuzzy msgid "Apply to all existing repository groups" -msgstr "Імпартаваць існы рэпазітар?" +msgstr "" #: kallithea/templates/admin/permissions/permissions_globals.html:41 msgid "Permissions for the Default user on new repository groups." @@ -3255,18 +3219,17 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:46 #: kallithea/templates/data_table/_dt_elements.html:209 msgid "User group" -msgstr "Група карыстачоў" +msgstr "Група карыстальнікаў" #: kallithea/templates/admin/permissions/permissions_globals.html:53 -#, fuzzy msgid "" "All default permissions on each user group will be reset to chosen " "permission, note that all custom default permission on user groups will " "be lost" msgstr "" -"Абраныя прывілеі будуць усталяваны па змаўчанні для кожнай групы " -"карыстачоў. Улічыце, што раней усталяваныя прывілеі па змаўчанні для груп" -" карыстачоў будуць скінуты" +"Выбраныя прывілеі будуць усталяваныя па змоўчанні для кожнай групы " +"карыстальнікаў. Улічыце, што раней усталяваныя прывілеі па змоўчанні для " +"груп карыстальнікаў будуць скінутыя" #: kallithea/templates/admin/permissions/permissions_globals.html:54 msgid "Apply to all existing user groups" @@ -3277,9 +3240,8 @@ msgstr "" #: kallithea/templates/admin/permissions/permissions_globals.html:60 -#, fuzzy msgid "Top level repository creation" -msgstr "Стварэнне рэпазітара" +msgstr "" #: kallithea/templates/admin/permissions/permissions_globals.html:64 msgid "Enable this to allow non-admins to create repositories at the top level." @@ -3304,7 +3266,7 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:79 msgid "User group creation" -msgstr "Стварэнне груп карыстачоў" +msgstr "Стварэнне груп карыстальнікаў" #: kallithea/templates/admin/permissions/permissions_globals.html:83 msgid "Enable this to allow non-admins to create user groups." @@ -3328,7 +3290,7 @@ #: kallithea/templates/admin/permissions/permissions_ips.html:13 #: kallithea/templates/admin/users/user_edit_ips.html:23 -#, fuzzy, python-format +#, python-format msgid "Confirm to delete this IP address: %s" msgstr "Пацвердзіце выдаленне IP %s" @@ -3386,7 +3348,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3445,9 +3407,8 @@ #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11 #: kallithea/templates/admin/repos/repo_edit_permissions.html:12 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11 -#, fuzzy msgid "User/User Group" -msgstr "групы карыстальнікаў" +msgstr "Карыстальнік/група карыстальнікаў" #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45 @@ -3455,9 +3416,8 @@ #: kallithea/templates/admin/repos/repo_edit_permissions.html:37 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45 -#, fuzzy msgid "Default" -msgstr "па змаўчанні" +msgstr "Па змоўчанні" #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71 @@ -3465,9 +3425,8 @@ #: kallithea/templates/admin/repos/repo_edit_permissions.html:68 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71 -#, fuzzy msgid "Revoke" -msgstr "адклікаць" +msgstr "Адклікаць" #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97 #: kallithea/templates/admin/repos/repo_edit_permissions.html:94 @@ -3476,9 +3435,8 @@ msgstr "Дадаць новы" #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103 -#, fuzzy msgid "Apply to children" -msgstr "дастасаваць да даччыным" +msgstr "" #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107 msgid "Both" @@ -3504,7 +3462,7 @@ #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53 msgid "Confirm to delete this group" -msgstr "Пацвердзіце выдаленне гэтай групы карыстачоў" +msgstr "Пацвердзіце выдаленне гэтай групы карыстальнікаў" #: kallithea/templates/admin/repo_groups/repo_group_show.html:4 #, python-format @@ -3513,7 +3471,7 @@ #: kallithea/templates/admin/repo_groups/repo_group_show.html:9 msgid "Home" -msgstr "Дахаты" +msgstr "Дамоў" #: kallithea/templates/admin/repo_groups/repo_group_show.html:13 msgid "with" @@ -3528,9 +3486,8 @@ msgstr "Лік рэпазітароў верхняга ўзроўня" #: kallithea/templates/admin/repos/repo_add_base.html:17 -#, fuzzy msgid "Clone remote repository" -msgstr "[створаны] рэпазітар" +msgstr "" #: kallithea/templates/admin/repos/repo_add_base.html:22 msgid "" @@ -3626,19 +3583,19 @@ #: kallithea/templates/admin/repos/repo_edit_advanced.html:8 #: kallithea/templates/admin/repos/repo_edit_fork.html:9 msgid "Manually set this repository as a fork of another from the list." -msgstr "Уручную зрабіць гэты рэпазітар форкам абранага са спісу." +msgstr "Уручную зрабіць гэты рэпазітар форкам выбранага са спісу." #: kallithea/templates/admin/repos/repo_edit_advanced.html:22 msgid "Public Journal Visibility" -msgstr "Доступ да публічнага часопіса" +msgstr "Доступ да публічнага журналу" #: kallithea/templates/admin/repos/repo_edit_advanced.html:29 msgid "Remove from public journal" -msgstr "Выдаліць з агульнадаступнага часопіса" +msgstr "Выдаліць з агульнадаступнага журналу" #: kallithea/templates/admin/repos/repo_edit_advanced.html:34 msgid "Add to Public Journal" -msgstr "Дадаць у публічны часопіс" +msgstr "Дадаць у публічны журнал" #: kallithea/templates/admin/repos/repo_edit_advanced.html:40 msgid "" @@ -3646,7 +3603,7 @@ "public journal." msgstr "" "Усе выконваемыя з гэтым рэпазітаром дзеянні будуць адлюстроўвацца ў " -"публічным часопісе." +"публічным журнал." #: kallithea/templates/admin/repos/repo_edit_advanced.html:46 msgid "Change Locking" @@ -3654,12 +3611,17 @@ #: kallithea/templates/admin/repos/repo_edit_advanced.html:52 msgid "Confirm to unlock repository." -msgstr "Пацвердзіце здыманне блакавання з рэпазітара." +msgstr "Пацвердзіце разблакаванне рэпазітара." #: kallithea/templates/admin/repos/repo_edit_advanced.html:54 msgid "Unlock Repository" msgstr "Разблакаваць рэпазітар" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "Заблакавана %s %s" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "Пацвердзіце блакаванне рэпазітара." @@ -3696,15 +3658,15 @@ msgid_plural "This repository has %s forks" msgstr[0] "Дадзены рэпазітар мае %s копію" msgstr[1] "Дадзены рэпазітар мае %s копіі" -msgstr[2] "Дадзены рэпазітар мае %s дзід" +msgstr[2] "Дадзены рэпазітар мае %s копій" #: kallithea/templates/admin/repos/repo_edit_advanced.html:85 msgid "Detach forks" -msgstr "Адлучыць fork'і" +msgstr "Адлучыць форкі" #: kallithea/templates/admin/repos/repo_edit_advanced.html:86 msgid "Delete forks" -msgstr "Выдаліць fork'і" +msgstr "Выдаліць форкі" #: kallithea/templates/admin/repos/repo_edit_advanced.html:90 msgid "" @@ -3717,15 +3679,11 @@ msgid "Invalidate Repository Cache" msgstr "Скінуць кэш рэпазітара" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "Пацвердзіце скід кэша." - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " "repository will be cached again." -msgstr "Ручны скід кэша рэпазітара. Пры першым доступе кэш адновіцца." +msgstr "Ручное скіданне кэша рэпазітара. Пры першым доступе кэш адновіцца." #: kallithea/templates/admin/repos/repo_edit_caches.html:12 msgid "List of Cached Values" @@ -3781,36 +3739,31 @@ #: kallithea/templates/admin/repos/repo_edit_fields.html:66 msgid "Extra fields are disabled." -msgstr "Дадатковыя палі адключаны." +msgstr "Дадатковыя палі адключаныя." #: kallithea/templates/admin/repos/repo_edit_permissions.html:21 -#, fuzzy msgid "Private Repository" -msgstr "прыватны рэпазітар" +msgstr "Прыватны рэпазітар" #: kallithea/templates/admin/repos/repo_edit_remote.html:3 -#, fuzzy msgid "Remote repository URL" -msgstr "Рэпазітар %s створаны" +msgstr "URL аддаленага рэпазітара" #: kallithea/templates/admin/repos/repo_edit_remote.html:9 -#, fuzzy msgid "Pull Changes from Remote Repository" -msgstr "[занесены змены з выдаленага рэпазітара] у рэпазітар" +msgstr "Занесці змены з аддаленага рэпазітара" #: kallithea/templates/admin/repos/repo_edit_remote.html:11 -#, fuzzy msgid "Confirm to pull changes from remote repository." -msgstr "Пацвердзіце спампоўку змен." +msgstr "Пацвердзіце спампоўку змен з аддаленага рэпазітара." #: kallithea/templates/admin/repos/repo_edit_remote.html:17 msgid "This repository does not have a remote repository URL." msgstr "" #: kallithea/templates/admin/repos/repo_edit_settings.html:11 -#, fuzzy msgid "Permanent Repository ID" -msgstr "прыватны рэпазітар" +msgstr "Пастаяннае ШВ рэпазітара" #: kallithea/templates/admin/repos/repo_edit_settings.html:11 msgid "What is that?" @@ -3831,14 +3784,12 @@ msgstr "" #: kallithea/templates/admin/repos/repo_edit_settings.html:21 -#, fuzzy msgid "Remote repository" -msgstr "[створаны] рэпазітар" +msgstr "Аддалены рэпазітар" #: kallithea/templates/admin/repos/repo_edit_settings.html:25 -#, fuzzy msgid "Repository URL" -msgstr "Рэпазітар" +msgstr "URL рэпазітара" #: kallithea/templates/admin/repos/repo_edit_settings.html:29 msgid "" @@ -3848,9 +3799,7 @@ #: kallithea/templates/admin/repos/repo_edit_settings.html:48 msgid "Default revision for files page, downloads, whoosh and readme" -msgstr "" -"Рэвізія па змаўчанні, з якой будзе вырабляцца выгрузка файлаў пры " -"спампоўцы" +msgstr "Рэвізія па змоўчанні, з якой будзе рабіцца выгрузка файлаў пры спампоўцы" #: kallithea/templates/admin/repos/repo_edit_settings.html:58 msgid "Change owner of this repository." @@ -3870,7 +3819,7 @@ #: kallithea/templates/admin/repos/repo_edit_statistics.html:10 msgid "Confirm to remove current statistics." -msgstr "Пацвердзіце скід статыстыкі." +msgstr "Пацвердзіце скіданне статыстыкі." #: kallithea/templates/admin/repos/repos.html:5 msgid "Repositories Administration" @@ -3975,7 +3924,7 @@ #: kallithea/templates/admin/settings/settings_hooks.html:19 msgid "Custom Hooks" -msgstr "Карыстацкія хуки" +msgstr "Карыстальніцкія хукі" #: kallithea/templates/admin/settings/settings_hooks.html:67 msgid "Failed to remove hook" @@ -3986,9 +3935,8 @@ msgstr "Опцыі перасканіравання" #: kallithea/templates/admin/settings/settings_mapping.html:11 -#, fuzzy msgid "Delete records of missing repositories" -msgstr "Пошук па рэпазітарах" +msgstr "Сцерці запісы пра выдаленыя рэпазітары" #: kallithea/templates/admin/settings/settings_mapping.html:13 msgid "" @@ -4049,12 +3997,11 @@ #: kallithea/templates/admin/settings/settings_system.html:4 msgid "Kallithea version" -msgstr "" +msgstr "Версія Kallithea" #: kallithea/templates/admin/settings/settings_system.html:4 -#, fuzzy msgid "Check for updates" -msgstr "праверыць наяўнасць абнаўленняў" +msgstr "Праверыць наяўнасць абнаўленняў" #: kallithea/templates/admin/settings/settings_system.html:5 msgid "Kallithea configuration file" @@ -4112,11 +4059,11 @@ #: kallithea/templates/admin/settings/settings_vcs.html:28 msgid "Log user push commands" -msgstr "Лагіраваць карыстацкія каманды адпраўкі" +msgstr "Лагаваць карыстацкія каманды адпраўкі" #: kallithea/templates/admin/settings/settings_vcs.html:32 msgid "Log user pull commands" -msgstr "Лагіраваць карыстацкія каманды атрымання" +msgstr "Лагаваць карыстацкія каманды атрымання" #: kallithea/templates/admin/settings/settings_vcs.html:36 msgid "Update repository after push (hg update)" @@ -4256,9 +4203,8 @@ msgstr "Паказваць абразкі публічных рэпазітароў." #: kallithea/templates/admin/settings/settings_visual.html:92 -#, fuzzy msgid "Meta Tagging" -msgstr "Метатэгіраванне" +msgstr "Метатэгаванне" #: kallithea/templates/admin/settings/settings_visual.html:97 msgid "Stylify recognised meta tags:" @@ -4272,7 +4218,7 @@ #: kallithea/templates/admin/user_groups/user_group_add.html:5 msgid "Add user group" -msgstr "Дадаць групу карыстачоў" +msgstr "Дадаць групу карыстальнікаў" #: kallithea/templates/admin/user_groups/user_group_add.html:10 #: kallithea/templates/admin/user_groups/user_group_edit.html:11 @@ -4297,9 +4243,8 @@ msgstr "" #: kallithea/templates/admin/user_groups/user_group_edit.html:33 -#, fuzzy msgid "Show Members" -msgstr "удзельнікі" +msgstr "Паказаць удзельнікаў" #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1 #, python-format @@ -4316,7 +4261,7 @@ #: kallithea/templates/data_table/_dt_elements.html:174 #, python-format msgid "Confirm to delete this user group: %s" -msgstr "Пацвердзіце выдаленне наступнай групы карыстачоў: %s" +msgstr "Пацвердзіце выдаленне наступнай групы карыстальнікаў: %s" #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21 msgid "Delete this user group" @@ -4336,23 +4281,23 @@ #: kallithea/templates/admin/user_groups/user_groups.html:5 msgid "User Groups Administration" -msgstr "Адміністраванне груп карыстачоў" +msgstr "Адміністраванне груп карыстальнікаў" #: kallithea/templates/admin/users/user_add.html:5 msgid "Add user" -msgstr "Дадаць карыстача" +msgstr "Дадаць карыстальніка" #: kallithea/templates/admin/users/user_add.html:10 #: kallithea/templates/admin/users/user_edit.html:11 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/base/base.html:62 msgid "Users" -msgstr "Карыстачы" +msgstr "Карыстальнікі" #: kallithea/templates/admin/users/user_add.html:12 #: kallithea/templates/admin/users/users.html:24 msgid "Add User" -msgstr "Дадаць карыстача" +msgstr "Дадаць карыстальніка" #: kallithea/templates/admin/users/user_add.html:50 msgid "Password confirmation" @@ -4390,7 +4335,7 @@ #: kallithea/templates/data_table/_dt_elements.html:158 #, python-format msgid "Confirm to delete this user: %s" -msgstr "Пацвердзіце выдаленне карыстача %s" +msgstr "Пацвердзіце выдаленне карыстальніка %s" #: kallithea/templates/admin/users/user_edit_advanced.html:23 msgid "Delete this user" @@ -4419,7 +4364,7 @@ #: kallithea/templates/admin/users/users.html:5 msgid "Users Administration" -msgstr "Адміністраванне карыстачоў" +msgstr "Адміністраванне карыстальнікаў" #: kallithea/templates/admin/users/users.html:56 msgid "Auth Type" @@ -4468,21 +4413,17 @@ msgid "Files" msgstr "Файлы" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "Пераключыцца на" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "Опцыі" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "Параўнаць форк" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4492,118 +4433,122 @@ msgid "Compare" msgstr "Параўнаць" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "Пошук" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "Разблакаваць" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "Заблакаваць" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "Назіраць" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "Не назіраць" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "Не назіраць" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "Форк" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" -msgstr "Стварыць Pull запыт" - -#: kallithea/templates/base/base.html:183 +msgstr "Стварыць pull-запыт" + +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "Паказаць pull-запыты для %s" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "Пераключыцца на" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "Супадзенняў не знойдзена" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "Паказаць апошнюю актыўнасць" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" -msgstr "Агульнадаступны часопіс" - -#: kallithea/templates/base/base.html:233 +msgstr "Агульнадаступны журнал" + +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "Паказаць публічныя запісы" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "Gist" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "Усе публічныя Gist-запісы" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "Мае публічныя Gist-запісы" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "Мае прыватныя Gist-запісы" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "Пошук па рэпазітарах" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" -msgstr "Мае Pull-запыты" - -#: kallithea/templates/base/base.html:289 +msgstr "Мае pull-запыты" + +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "Не аўтарызаваны" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "Аўтарызавацца" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" -msgstr "Забыліся пароль?" - -#: kallithea/templates/base/base.html:346 +msgstr "Забыліся на пароль?" + +#: kallithea/templates/base/base.html:434 msgid "Log Out" -msgstr "Выйсце" - -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "Супадзенняў не знойдзена" - -#: kallithea/templates/base/base.html:524 +msgstr "Выйсці" + +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "Гарачыя клавішы" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" #: kallithea/templates/base/default_perms_box.html:14 -#, fuzzy msgid "Inherit defaults" -msgstr "Значэнні па змаўчанні" +msgstr "Ужываць значэнні па змоўчанні" #: kallithea/templates/base/default_perms_box.html:19 #, python-format @@ -4618,15 +4563,15 @@ #: kallithea/templates/base/default_perms_box.html:33 msgid "Select this option to allow repository creation for this user" -msgstr "Опцыя дазваляе карыстачу ствараць рэпазітары" +msgstr "Опцыя дазваляе карыстальніку ствараць рэпазітары" #: kallithea/templates/base/default_perms_box.html:40 msgid "Create user groups" -msgstr "Ствараць групы карыстачоў" +msgstr "Ствараць групы карыстальнікаў" #: kallithea/templates/base/default_perms_box.html:45 msgid "Select this option to allow user group creation for this user" -msgstr "Опцыя дазваляе карыстачу ствараць групы карыстачоў" +msgstr "Опцыя дазваляе карыстальніку ствараць групы карыстальнікаў" #: kallithea/templates/base/default_perms_box.html:52 msgid "Fork repositories" @@ -4635,7 +4580,7 @@ #: kallithea/templates/base/default_perms_box.html:57 msgid "Select this option to allow repository forking for this user" msgstr "" -"Абярыце гэту опцыю каб дазволіць дадзенаму карыстачу ствараць fork'і " +"Абярыце гэту опцыю каб дазволіць дадзенаму карыстальніку ствараць форкі " "рэпазітароў" #: kallithea/templates/base/perms_summary.html:13 @@ -4645,7 +4590,7 @@ #: kallithea/templates/base/perms_summary.html:22 msgid "No permissions defined yet" -msgstr "Прывілеі яшчэ не прызначаны" +msgstr "Прывілеі яшчэ не прызначаныя" #: kallithea/templates/base/perms_summary.html:30 #: kallithea/templates/base/perms_summary.html:54 @@ -4699,20 +4644,16 @@ msgstr "Няма супадзенняў" #: kallithea/templates/base/root.html:31 -#, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" -msgstr "Каментар у pull-запыце" +msgstr "Стварыць новы pull-запыт з {0}" #: kallithea/templates/base/root.html:32 msgid "Open New Pull Request for {0} → {1}" -msgstr "" +msgstr "Стварыць новы pull-запыт для {0} → {1}" #: kallithea/templates/base/root.html:33 -#, fuzzy -#| msgid "Show Selected Changesets __S → __E" msgid "Show Selected Changesets {0} → {1}" -msgstr "Паказаць абраныя наборы змен: __S → __E" +msgstr "Паказаць выбраныя наборы змен: {0} → {1}" #: kallithea/templates/base/root.html:34 msgid "Selection Link" @@ -4720,12 +4661,13 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "Згарнуць параўнанне" #: kallithea/templates/base/root.html:36 msgid "Expand Diff" -msgstr "Расчыніць параўнанне" +msgstr "Разгарнуць параўнанне" #: kallithea/templates/base/root.html:37 msgid "Failed to revoke permission" @@ -4745,7 +4687,7 @@ #: kallithea/templates/base/root.html:42 msgid "Specify changeset" -msgstr "Абраць набор змен" +msgstr "Выбраць набор змен" #: kallithea/templates/bookmarks/bookmarks.html:5 #, python-format @@ -4754,7 +4696,7 @@ #: kallithea/templates/bookmarks/bookmarks.html:26 msgid "Compare Bookmarks" -msgstr "" +msgstr "Параўнаць закладкі" #: kallithea/templates/bookmarks/bookmarks.html:53 #: kallithea/templates/bookmarks/bookmarks_data.html:10 @@ -4782,11 +4724,11 @@ #: kallithea/templates/branches/branches.html:5 #, python-format msgid "%s Branches" -msgstr "Галінкі %s" +msgstr "%s Галіны" #: kallithea/templates/branches/branches.html:26 msgid "Compare Branches" -msgstr "" +msgstr "Параўнаць галіны" #: kallithea/templates/changelog/changelog.html:6 #, python-format @@ -4823,59 +4765,61 @@ #: kallithea/templates/changelog/changelog.html:66 #: kallithea/templates/files/files.html:29 msgid "Branch filter:" -msgstr "Адфільтраваць галінку:" +msgstr "Адфільтраваць галіну:" #: kallithea/templates/changelog/changelog.html:92 #: kallithea/templates/changelog/changelog_summary_data.html:20 #, fuzzy, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" -"Статут набору змен: %s?\n" -"Клікніце, каб перайсці да адпаведнага pull-request'у #%s" +"Статус набору змен: %s?\n" +"Клікніце, каб перайсці да адпаведнага pull-запыту %s" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "Статут набору змен: %s" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +msgid "Changeset status: %s by %s" +msgstr "Статус набору змен: %s" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:124 +msgstr "Разгарнуць паведамленне" + +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "Каментары адсутнічаюць" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "Закладка %s" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" -msgstr "Пазнака %s" - -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +msgstr "Тэг %s" + +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" -msgstr "Галінка %s" - -#: kallithea/templates/changelog/changelog.html:310 +msgstr "Галіна %s" + +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "Змен яшчэ няма" @@ -4891,7 +4835,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "Дададзена" @@ -4921,22 +4865,21 @@ msgid "Refs" msgstr "Спасылкі" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "Дадаць ці загрузіць файлы праз Kallithea" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "Дадаць новы файл" -#: kallithea/templates/changelog/changelog_summary_data.html:90 -#, fuzzy +#: kallithea/templates/changelog/changelog_summary_data.html:93 msgid "Push new repository" msgstr "Адправіць новы рэпазітар" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "Існы рэпазітар?" @@ -4954,32 +4897,31 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" -msgstr "Статут змен" +msgstr "Статус змен" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "Адлюстраваць у фармаце diff" #: kallithea/templates/changeset/changeset.html:57 msgid "Patch diff" -msgstr "Ужыць рознаснае выпраўленне (Patch diff)" +msgstr "Patch diff" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" -msgstr "Запампаваць diff" +msgstr "Спампаваць diff" #: kallithea/templates/changeset/changeset.html:89 #: kallithea/templates/changeset/changeset_range.html:88 -#, fuzzy msgid "Merge" -msgstr "звесці" +msgstr "" #: kallithea/templates/changeset/changeset.html:123 msgid "Grafted from:" @@ -4990,135 +4932,136 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:135 -#, fuzzy msgid "Replaced by:" -msgstr "Створана" +msgstr "Заменена:" #: kallithea/templates/changeset/changeset.html:149 -#, fuzzy msgid "Preceded by:" -msgstr "Створана" +msgstr "Замяняе:" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" msgstr[0] "%s файл зменены" -msgstr[1] "%s файлаў зменена" -msgstr[2] "%s файла зменена" +msgstr[1] "%s файлы зменена" +msgstr[2] "%s файлаў зменена" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" msgstr[0] "%s файл зменены: %s даданне, %s выдаленне" -msgstr[1] "%s файла зменена: %s даданні, %s выдаленні" +msgstr[1] "%s файлы зменена: %s даданні, %s выдаленні" msgstr[2] "%s файлаў зменена: %s даданняў, %s выдаленняў" #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "Паказаць поўны diff" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 -#, fuzzy +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 msgid "No revisions" -msgstr "няма рэвізій" +msgstr "Няма рэвізій" #: kallithea/templates/changeset/changeset_file_comment.html:21 -#, fuzzy msgid "on pull request" -msgstr "Каментар у pull-запыце" +msgstr "у pull-запыце" #: kallithea/templates/changeset/changeset_file_comment.html:22 msgid "No title" msgstr "Няма загалоўка" #: kallithea/templates/changeset/changeset_file_comment.html:24 -#, fuzzy msgid "on this changeset" -msgstr "Няма змен" - -#: kallithea/templates/changeset/changeset_file_comment.html:30 +msgstr "на змене" + +#: kallithea/templates/changeset/changeset_file_comment.html:31 msgid "Delete comment?" msgstr "Выдаліць каментар?" -#: kallithea/templates/changeset/changeset_file_comment.html:37 -#, fuzzy +#: kallithea/templates/changeset/changeset_file_comment.html:39 msgid "Status change" -msgstr "Апошнія змены" +msgstr "Змена статусу" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." -msgstr "Каментар да радка {1}." +msgid "Commenting on line." +msgstr "Каментар да радка." #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "Парсінг каментароў выкананы з выкарыстаннем сінтаксісу %s з падтрымкай %s." - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" -"Выкарыстоўвайце @імя_карыстача ў тэксце, каб адправіць абвестку пэўнаму " -"карыстачу" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "Папярэдні прагляд каментара" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +#, fuzzy +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" +"Выкарыстоўвайце @імя_карыстальніка ў тэксце, каб адправіць паведамленне " +"пэўнаму карыстальніку" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "Змяніць статус рэвізіі" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "Без змен" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Каментар у pull-запыце" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "Закрыць" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." -msgstr "Ужыванне..." - -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +msgstr "Адпраўка..." + +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "Каментаваць" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "Прадпрагляд" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "Вам неабходна аўтарызавацца, каб пакідаць каментары." -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" -msgstr "Аўтарызавацца цяпер" - -#: kallithea/templates/changeset/changeset_file_comment.html:94 +msgstr "Аўтарызавацца зараз" + +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "Схаваць" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "%d каментар" -msgstr[1] "%d каментара" +msgstr[1] "%d каментары" msgstr[2] "%d каментароў" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, python-format msgid "%d inline" msgid_plural "%d inline" -msgstr[0] "%d да радка" -msgstr[1] "%d да радкоў" -msgstr[2] "%d да радкоў" - -#: kallithea/templates/changeset/changeset_file_comment.html:108 +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" @@ -5126,29 +5069,6 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -#, fuzzy -msgid "Use @username inside this text to notify another user." -msgstr "" -"Выкарыстоўвайце @імя_карыстача ў тэксце, каб адправіць абвестку пэўнаму " -"карыстачу" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -msgid "Set changeset status" -msgstr "Змяніць статус рэвізіі" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "Без змен" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -msgid "Close" -msgstr "Зачыніць" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5158,29 +5078,28 @@ msgid "Files affected" msgstr "Закранутыя файлы" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "Выдалены" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "Пераназваны" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "Паказаць поўны diff для гэтага файла" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "Паказаць поўны diff для гэтага файла" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "Паказаць каментары ў радках" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "Выдалены" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "Пераназваны" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "Няма змен" @@ -5189,6 +5108,11 @@ msgid "Ancestor" msgstr "Продак" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "Статус набору змен: %s" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5201,29 +5125,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "Паказаць merge diff" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "Агульны продак" -#: kallithea/templates/compare/compare_cs.html:90 +#: kallithea/templates/compare/compare_cs.html:99 msgid "No common ancestor found - repositories are unrelated" msgstr "" -#: kallithea/templates/compare/compare_cs.html:98 +#: kallithea/templates/compare/compare_cs.html:107 msgid "is" msgstr "адстае на" -#: kallithea/templates/compare/compare_cs.html:99 +#: kallithea/templates/compare/compare_cs.html:108 #, python-format msgid "%s changesets" msgstr "%s змен" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "ад" @@ -5234,29 +5158,29 @@ msgstr "%s Параўнаць" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "Параўнаць рэвізіі" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "Паказаць %s commit" -msgstr[1] "Паказаць %s commit'а" +msgstr[1] "Паказаць %s commit'ы" msgstr[2] "Паказаць %s commit'аў" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "Паказаць поўны diff" @@ -5300,7 +5224,7 @@ #: kallithea/templates/email_templates/changeset_comment.html:12 msgid "The changeset status was changed to" -msgstr "Стан набору змен зменены на" +msgstr "Статус набору змен зменены на" #: kallithea/templates/email_templates/main.html:6 msgid "This is an automatic notification. Don't reply to this mail." @@ -5312,22 +5236,26 @@ msgstr "Добры дзень, %s" #: kallithea/templates/email_templates/password_reset.html:6 -#, fuzzy -#| msgid "We received a request to create a new password for your account." msgid "We have received a request to reset the password for your account." -msgstr "Мы атрымалі запыт на стварэнне новага пароля для вашага акаўнта." - -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +msgstr "Мы атрымалі запыт на скіданне пароля для вашага акаўнта." + +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5358,7 +5286,7 @@ #: kallithea/templates/email_templates/registration.html:6 msgid "View this user here" -msgstr "Падрабязней пра карыстача" +msgstr "Падрабязней пра карыстальніка" #: kallithea/templates/files/diff_2way.html:15 #, python-format @@ -5410,14 +5338,15 @@ msgstr "Стварыць новы файл" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "Рэжым новага файла" +#, fuzzy +msgid "New file type" +msgstr "Тып новага файла" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 #: kallithea/templates/files/files_edit.html:67 msgid "Commit Changes" -msgstr "Ужыць змены" +msgstr "Захаваць змены" #: kallithea/templates/files/files_browser.html:33 msgid "Previous revision" @@ -5429,7 +5358,7 @@ #: kallithea/templates/files/files_browser.html:41 msgid "Follow current branch" -msgstr "Адсочваць дадзеную галінку" +msgstr "Адсочваць дадзеную галіну" #: kallithea/templates/files/files_browser.html:44 msgid "Search File List" @@ -5527,11 +5456,11 @@ #: kallithea/templates/files/files_source.html:41 msgid "Editing binary files not allowed" -msgstr "Рэдагаванне бінарных файлаў забаронена" +msgstr "Рэдагаванне бінарных файлаў забароненае" #: kallithea/templates/files/files_source.html:44 msgid "Editing files allowed only when on branch head revision" -msgstr "Рэдагаванне файлаў дазволена толькі ў HEAD-рэвізіі дадзенай галінкі" +msgstr "Рэдагаванне файлаў дазволенае толькі ў HEAD-рэвізіі дадзенай галіны" #: kallithea/templates/files/files_source.html:45 msgid "Deleting files allowed only when on branch head revision" @@ -5542,9 +5471,20 @@ msgid "Binary file (%s)" msgstr "Бінарны файл (%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" -msgstr "Файл занадта вялікай для адлюстравання" +#: kallithea/templates/files/files_source.html:74 +#, fuzzy +msgid "File is too big to display." +msgstr "Файл занадта вялікі для адлюстравання" + +#: kallithea/templates/files/files_source.html:76 +#, fuzzy +msgid "Show full annotation anyway." +msgstr "Паказаць поўны diff" + +#: kallithea/templates/files/files_source.html:78 +#, fuzzy +msgid "Show as raw." +msgstr "Паказаць толькі тэкст" #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" @@ -5629,11 +5569,11 @@ #: kallithea/templates/forks/forks_data.html:30 msgid "There are no forks yet" -msgstr "Форкі яшчэ не створаны" +msgstr "Форкі яшчэ не створаныя" #: kallithea/templates/journal/journal.html:21 msgid "ATOM journal feed" -msgstr "Стужка часопіса ATOM" +msgstr "Стужка часопіса Atom" #: kallithea/templates/journal/journal.html:22 msgid "RSS journal feed" @@ -5649,7 +5589,7 @@ #: kallithea/templates/journal/public_journal.html:13 msgid "ATOM public journal feed" -msgstr "Агульная стужка часопіса ATOM" +msgstr "Агульная стужка часопіса Atom" #: kallithea/templates/journal/public_journal.html:14 msgid "RSS public journal feed" @@ -5693,9 +5633,8 @@ msgstr "Запісы отсуствуют" #: kallithea/templates/pullrequests/pullrequest_data.html:14 -#, fuzzy msgid "Vote" -msgstr "адклікаць" +msgstr "Галасаваць" #: kallithea/templates/pullrequests/pullrequest_data.html:18 msgid "From" @@ -5733,19 +5672,19 @@ msgstr "Пацвердзіце выдаленне гэтага pull-request'а" #: kallithea/templates/pullrequests/pullrequest_data.html:70 -#, fuzzy, python-format +#, python-format msgid "Confirm again to delete this pull request with %s comments" -msgstr "Пацвердзіце выдаленне гэтага pull-request'а" +msgstr "Пацвердзіце выдаленне гэтага pull-запыту з %s каментарамі" #: kallithea/templates/pullrequests/pullrequest_show.html:6 -#, fuzzy, python-format +#, python-format msgid "%s Pull Request %s" -msgstr "%s Pull-запыт #%s" +msgstr "%s зull-запыт %s" #: kallithea/templates/pullrequests/pullrequest_show.html:10 -#, fuzzy, python-format +#, python-format msgid "Pull request %s from %s#%s" -msgstr "Pull-запыты №%s ад %s#%s" +msgstr "Pull-запыт %s ад %s#%s" #: kallithea/templates/pullrequests/pullrequest_show.html:57 msgid "Summarize the changes" @@ -5769,7 +5708,7 @@ msgid "%d reviewer" msgid_plural "%d reviewers" msgstr[0] "%d рэцэнзент" -msgstr[1] "%d рэцэнзента" +msgstr[1] "%d рэцэнзенты" msgstr[2] "%d рэцэнзентаў" #: kallithea/templates/pullrequests/pullrequest_show.html:99 @@ -5810,56 +5749,63 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "Рэцэнзенты pull-запытаў" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 msgid "Remove reviewer" msgstr "Выдаліць рэцэнзента" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -msgid "Potential Reviewers" -msgstr "Патэнцыйныя рэцэнзенты" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +msgid "Potential Reviewers" +msgstr "Патэнцыйныя рэцэнзенты" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "Захаваць змены" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#, fuzzy +msgid "Save Updates as New Pull Request" +msgstr "Pull-запыт створаны паспяхова" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 msgid "Cancel Changes" msgstr "Адмяніць змены" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "" #: kallithea/templates/pullrequests/pullrequest_show_all.html:6 #, python-format msgid "%s Pull Requests" -msgstr "%s Запыты на занясенне змен" +msgstr "%s pull-запыты" #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 #, python-format -msgid "Pull Requests from %s'" -msgstr "Pull-запыты ад %s" +msgid "Pull Requests from '%s'" +msgstr "Pull-запыты ад '%s'" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 #, python-format msgid "Pull Requests to '%s'" -msgstr "Pull-запыты для %s" +msgstr "Pull-запыты да '%s'" #: kallithea/templates/pullrequests/pullrequest_show_all.html:32 msgid "Open New Pull Request" @@ -5913,7 +5859,7 @@ #: kallithea/templates/search/search.html:65 msgid "File contents" -msgstr "Змесціва файлаў" +msgstr "Змест файлаў" #: kallithea/templates/search/search.html:66 msgid "Commit messages" @@ -5927,7 +5873,7 @@ #: kallithea/templates/search/search_content.html:21 #: kallithea/templates/search/search_path.html:15 msgid "Permission denied" -msgstr "Недастаткова мае рацыю" +msgstr "Недастаткова правоў" #: kallithea/templates/summary/statistics.html:4 #, python-format @@ -5938,7 +5884,7 @@ #: kallithea/templates/summary/summary.html:39 #, python-format msgid "%s ATOM feed" -msgstr "ATOM стужка рэпазітара %s" +msgstr "Atom стужка рэпазітара %s" #: kallithea/templates/summary/statistics.html:17 #: kallithea/templates/summary/summary.html:40 @@ -6022,15 +5968,15 @@ #: kallithea/templates/summary/summary.html:72 msgid "Clone URL" -msgstr "Спасылка для кланавання" +msgstr "URL для кланавання" #: kallithea/templates/summary/summary.html:78 msgid "Show by Name" -msgstr "Паказаць па імі" +msgstr "Паводле імя" #: kallithea/templates/summary/summary.html:79 msgid "Show by ID" -msgstr "Паказаць па ID" +msgstr "Паводле ID" #: kallithea/templates/summary/summary.html:92 msgid "Trending files" @@ -6038,7 +5984,7 @@ #: kallithea/templates/summary/summary.html:108 msgid "Download" -msgstr "Запампаваць" +msgstr "Спампаваць" #: kallithea/templates/summary/summary.html:112 msgid "There are no downloads yet" @@ -6046,20 +5992,19 @@ #: kallithea/templates/summary/summary.html:114 msgid "Downloads are disabled for this repository" -msgstr "Спампоўка адключана ў гэтым рэпазітары" +msgstr "Спампоўванне адключанае ў гэтым рэпазітары" #: kallithea/templates/summary/summary.html:120 msgid "Download as zip" -msgstr "Запампаваць у zip" +msgstr "Спампаваць у zip" #: kallithea/templates/summary/summary.html:125 msgid "Check this to download archive with subrepos" msgstr "Адзначце для спампоўкі архіва з даччынымі рэпазітарамі" #: kallithea/templates/summary/summary.html:125 -#, fuzzy msgid "With subrepos" -msgstr "з даччынымі рэпазітарамі" +msgstr "З даччынымі рэпазітарамі" #: kallithea/templates/summary/summary.html:156 msgid "Repository Size" @@ -6086,12 +6031,12 @@ #: kallithea/templates/summary/summary.html:293 #, python-format msgid "Download %s as %s" -msgstr "Запампаваць %s як %s" +msgstr "Спампаваць %s як %s" #: kallithea/templates/tags/tags.html:5 #, python-format msgid "%s Tags" -msgstr "%s Пазнак" +msgstr "%s Тэгаў" #: kallithea/templates/tags/tags.html:26 msgid "Compare Tags" @@ -6122,19 +6067,16 @@ #~ msgstr "Рэпазітар %s" #~ msgid "You can't edit this user" -#~ msgstr "Вы не можаце рэдагаваць дадзенага карыстача" +#~ msgstr "Вы не можаце рэдагаваць дадзенага карыстальніка" #~ msgid "No Files" #~ msgstr "Файлаў няма" -#~ msgid "" -#~ msgstr "" - #~ msgid "Username \"%(username)s\" is forbidden" #~ msgstr "Імя \"%(username)s\" адхілена" #~ msgid "invalid user name" -#~ msgstr "няслушнае імя карыстача" +#~ msgstr "няслушнае імя карыстальніка" #~ msgid "Your account is disabled" #~ msgstr "Ваш акаўнт выключаны" @@ -6185,10 +6127,10 @@ #~ msgstr "Перазапісаць існыя налады" #~ msgid "Default IP Whitelist for All Users" -#~ msgstr "Белы спіс IP для ўсіх карыстачоў" +#~ msgstr "Белы спіс IP для ўсіх карыстальнікаў" #~ msgid "Default User Permissions Overview" -#~ msgstr "Агляд мае рацыю карыстачоў па змаўчанні" +#~ msgstr "Агляд мае рацыю карыстальнікаў па змаўчанні" #~ msgid "none" #~ msgstr "нічога" @@ -6313,27 +6255,6 @@ #~ msgid "reviewer" #~ msgstr "рэцэнзент" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "Пароль скінуты паспяхова, новы пароль быў адпраўлены на ваш email" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "Ваш новы пароль" @@ -6351,10 +6272,31 @@ #~ msgid "Please ignore this email if you did not request a new password ." #~ msgstr "" -#~ "Калі ласка, праігнаруйце дадзенае " -#~ "паведамленне, калі вы не запытвалі новы" -#~ " пароль." #~ msgid "Created by" #~ msgstr "Створана" +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "Гэты pull-запыт можа быць абноўлены з %s:" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "Пацвердзіце скід кэша." + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "Папярэдні прагляд каментара" + +#~ msgid "Preview" +#~ msgstr "Прадпрагляд" + +#~ msgid "New file mode" +#~ msgstr "Рэжым новага файла" + +#~ msgid "Save as New Pull Request" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/cs/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/cs/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/cs/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -7,11 +7,11 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2015-11-12 08:51+0000\n" "Last-Translator: Michal Čihař \n" "Language-Team: Czech " -"\n" +"\n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,12 +19,12 @@ "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Weblate 2.5-dev\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -36,35 +36,29 @@ msgid "None" msgstr "" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(zavřeno)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "Taková revize neexistuje" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "" @@ -117,10 +111,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "" @@ -129,111 +123,111 @@ msgid "%s committed on %s" msgstr "" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "Klikněte pro přidání nového souboru" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "Zatím nejsou žádné soubory. %s" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Žádné změny" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Přidaný soubor přes Kallithea" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Žádný obsah" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "Stahování vypnuto" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Neznámá revize %s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Prázdný repozitář" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Změny" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "Větve" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Tagy" @@ -247,7 +241,7 @@ msgid "Groups" msgstr "Skupiny" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -255,23 +249,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Repozitáře" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "Větev" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "Tag" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "Záložka" @@ -282,158 +280,163 @@ msgstr "" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "Špatná captcha" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "" - -#: kallithea/controllers/login.py:202 +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" msgstr "" -#: kallithea/controllers/login.py:251 +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" msgstr "" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "Úspěšně aktualizované heslo" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (zavřené)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Záložky" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 -msgid "No permission to change pull request status" -msgstr "" - #: kallithea/controllers/pullrequests.py:727 +msgid "No permission to change pull request status" +msgstr "" + +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "Úspěšně aktualizované heslo" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "" @@ -449,12 +452,12 @@ msgid "An error occurred during search operation." msgstr "Došlo k chybě při vyhledávání." -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "" @@ -475,64 +478,64 @@ msgid "Error occurred during update of defaults" msgstr "" +#: kallithea/controllers/admin/gists.py:58 +#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "" + #: kallithea/controllers/admin/gists.py:59 -#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/my_account.py:244 #: kallithea/controllers/admin/users.py:285 -msgid "Forever" -msgstr "" +msgid "5 minutes" +msgstr "5 minut" #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" -msgstr "5 minut" +msgid "1 hour" +msgstr "1 hodina" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "1 hodina" +msgid "1 day" +msgstr "1 den" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "1 den" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "1 měsíc" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Došlo k chybě při vytváření gist" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "" @@ -547,7 +550,7 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "" @@ -557,33 +560,33 @@ msgstr "Došlo k chybě při aktualizaci hesla uživatele" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "Došlo k chybě při ukládání e-mailové adresy" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -633,10 +636,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "" @@ -667,7 +670,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "" @@ -679,7 +682,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "" @@ -700,242 +703,242 @@ msgid "Error occurred during update of permissions" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "Chyba při vytváření repozitáře %s" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "Nic" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 msgid "Repository has been locked" msgstr "Repozitář byl uzamčen" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 msgid "Repository has been unlocked" msgstr "Repozitář byl odemčen" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "" @@ -976,76 +979,80 @@ msgstr "" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "" @@ -1061,125 +1068,125 @@ msgid "No changes detected" msgstr "" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "" -#: kallithea/lib/helpers.py:696 -msgid "and" -msgstr "" - #: kallithea/lib/helpers.py:697 +msgid "and" +msgstr "" + +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "" @@ -1189,8 +1196,8 @@ msgstr "" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "" @@ -1214,7 +1221,7 @@ msgid "chmod" msgstr "" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1222,7 +1229,7 @@ "repositories" msgstr "" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" @@ -1230,7 +1237,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" @@ -1238,7 +1245,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" @@ -1246,7 +1253,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" @@ -1254,7 +1261,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" @@ -1262,7 +1269,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" @@ -1270,27 +1277,27 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "" @@ -1389,7 +1396,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "" @@ -1500,7 +1507,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "" @@ -1515,7 +1522,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "" @@ -1542,7 +1549,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "" @@ -1689,7 +1696,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "" @@ -1716,12 +1723,12 @@ msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1730,106 +1737,106 @@ msgid "on line %s" msgstr "" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 -msgid "Default user has read access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1669 -msgid "Default user has write access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" +msgid "Default user has read access to new repositories" msgstr "" #: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" +msgid "Default user has write access to new repositories" msgstr "" #: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" msgstr "" #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -msgid "Only admins can create repository groups" +msgid "Default user has read access to new user groups" msgstr "" #: kallithea/model/db.py:1683 -msgid "Non-admins can create repository groups" -msgstr "" - -#: kallithea/model/db.py:1685 -msgid "Only admins can create user groups" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" msgstr "" #: kallithea/model/db.py:1686 -msgid "Non-admins can create user groups" -msgstr "" - -#: kallithea/model/db.py:1688 -msgid "Only admins can create top level repositories" +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" msgstr "" #: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 #, fuzzy msgid "Only admins can fork repositories" msgstr "Chyba při vytváření repozitáře %s" -#: kallithea/model/db.py:1695 +#: kallithea/model/db.py:1699 #, fuzzy -msgid "Non-admins can can fork repositories" +msgid "Non-admins can fork repositories" msgstr "Chyba při vytváření repozitáře %s" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 msgid "User registration with manual account activation" msgstr "" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "" @@ -1852,7 +1859,7 @@ msgid "Enter %(min)i characters or more" msgstr "" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1945,7 +1952,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "" @@ -1978,15 +1985,15 @@ "owners or remove those user groups: %s" msgstr "" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -1997,168 +2004,168 @@ msgid "Value cannot be an empty list" msgstr "" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "" -#: kallithea/model/validators.py:153 -#, python-format -msgid "User group \"%(usergroup)s\" already exists" -msgstr "" - #: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" msgstr "" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy msgid "Invalid repository URL" msgstr "Prázdný repozitář" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" msgstr "" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2285,7 +2292,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "" @@ -2333,7 +2340,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2343,7 +2350,7 @@ msgstr "" #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "" @@ -2358,7 +2365,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "" @@ -2366,7 +2373,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "" @@ -2378,7 +2385,7 @@ msgid "Forgot your password ?" msgstr "" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "" @@ -2499,10 +2506,6 @@ msgid "There are no branches yet" msgstr "" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2731,12 +2734,12 @@ msgid "Never" msgstr "" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "" @@ -2759,7 +2762,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2847,7 +2850,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2867,8 +2871,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2895,13 +2897,12 @@ msgstr "" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "" @@ -3086,7 +3087,7 @@ msgstr "" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "" @@ -3104,7 +3105,7 @@ msgstr "" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "" @@ -3303,7 +3304,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3569,6 +3570,11 @@ msgid "Unlock Repository" msgstr "Prázdný repozitář" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 #, fuzzy msgid "Confirm to lock repository." @@ -3629,10 +3635,6 @@ msgid "Invalidate Repository Cache" msgstr "" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -4374,21 +4376,17 @@ msgid "Files" msgstr "" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4398,111 +4396,116 @@ msgid "Compare" msgstr "" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4605,7 +4608,6 @@ #: kallithea/templates/base/root.html:31 #, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "Změna stavu-> %s" @@ -4623,6 +4625,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "" @@ -4733,51 +4736,54 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Set changeset status" +msgid "Changeset status: %s by %s" +msgstr "Změny" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "" @@ -4793,7 +4799,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4823,22 +4829,22 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy msgid "Push new repository" msgstr "Prázdný repozitář" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "" @@ -4856,13 +4862,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -4872,7 +4878,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -4899,8 +4905,8 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -4909,8 +4915,8 @@ msgstr[2] "" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4920,13 +4926,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy msgid "No revisions" msgstr "Neznámá revize %s" @@ -4945,62 +4951,71 @@ msgid "on this changeset" msgstr "Žádné změny" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 #, fuzzy msgid "Delete comment?" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "Změna stavu-> %s" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +msgid "Commenting on line." msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +#, fuzzy +msgid "Set changeset status" +msgstr "Změny" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +#, fuzzy +msgid "No change" +msgstr "Žádné změny" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Změna stavu-> %s" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +#, fuzzy +msgid "Close" +msgstr "(zavřeno)" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" @@ -5008,7 +5023,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, fuzzy, python-format msgid "%d inline" msgid_plural "%d inline" @@ -5016,7 +5031,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" @@ -5024,29 +5039,6 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -#, fuzzy -msgid "Set changeset status" -msgstr "Změny" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -#, fuzzy -msgid "No change" -msgstr "Žádné změny" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -#, fuzzy -msgid "Close" -msgstr "(zavřeno)" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5056,29 +5048,28 @@ msgid "Files affected" msgstr "" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "" @@ -5087,6 +5078,11 @@ msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5099,29 +5095,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 #, fuzzy, python-format msgid "%s changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "" @@ -5132,20 +5128,20 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" @@ -5153,8 +5149,8 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5213,17 +5209,23 @@ msgid "We have received a request to reset the password for your account." msgstr "" -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5306,7 +5308,7 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" +msgid "New file type" msgstr "" #: kallithea/templates/files/files_add.html:64 @@ -5439,8 +5441,16 @@ msgid "Binary file (%s)" msgstr "" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "" + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." msgstr "" #: kallithea/templates/files/files_ypjax.html:5 @@ -5705,42 +5715,48 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 #, fuzzy msgid "Remove reviewer" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -#, fuzzy -msgid "Potential Reviewers" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +#, fuzzy +msgid "Potential Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 #, fuzzy msgid "Cancel Changes" msgstr "Žádné změny" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "" @@ -5750,9 +5766,9 @@ msgstr "" #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 -#, python-format -msgid "Pull Requests from %s'" -msgstr "" +#, fuzzy, python-format +msgid "Pull Requests from '%s'" +msgstr "Změna stavu-> %s" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 #, python-format @@ -6047,8 +6063,6 @@ #~ msgstr "" #~ msgid "" -#~ "_: \n" -#~ "" #~ msgstr "" #~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s" @@ -6324,39 +6338,12 @@ #~ msgid "Your password reset link was sent" #~ msgstr "" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "" #~ msgid "Your new Kallithea password:%s" #~ msgstr "" -#~ msgid "" -#~ "Password reset link will be sent " -#~ "to the email address matching your " -#~ "username." -#~ msgstr "" - #~ msgid "Open New Pull Request for Selected Changesets" #~ msgstr "" @@ -6377,3 +6364,51 @@ #~ msgid "Created by" #~ msgstr "" + +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "You can only edit files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "" + +#~ msgid "Commenting on line {1}." +#~ msgstr "" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "" + +#~ msgid "Preview" +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user." +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "File is too big to display" +#~ msgstr "" + +#~ msgid "Save as New Pull Request" +#~ msgstr "" + +#~ msgid "Pull Requests from %s'" +#~ msgstr "" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/de/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/de/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/de/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -7,24 +7,24 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" -"PO-Revision-Date: 2015-09-08 10:56+0200\n" -"Last-Translator: Robert Rauch \n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" +"PO-Revision-Date: 2016-03-21 00:29+0000\n" +"Last-Translator: Robert Martinez \n" "Language-Team: German " -"\n" +"\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.4-dev\n" - -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:241 kallithea/lib/base.py:512 +"X-Generator: Weblate 2.6-dev\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Es gibt noch keine Änderungssätze" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -36,50 +36,40 @@ msgid "None" msgstr "Keine" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:197 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(geschlossen)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "Zeige unsichtbare Zeichen" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "Ignoriere unsichtbare Zeichen" -#: kallithea/controllers/changeset.py:169 -#, python-format -#| msgid "increase diff context to %(num)s lines" +#: kallithea/controllers/changeset.py:168 +#, python-format msgid "Increase diff context to %(num)s lines" msgstr "Erhöhe diff-Kontext auf %(num)s Zeilen" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:97 -#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:743 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "Die angegebene Version existiert nicht in diesem Repository" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" -"Eine Änderung des Status eines Änderungssatzes, der mit einem geschlossen" -" Pull-Request assoziert ist, ist nicht erlaubt" - -#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:42 +#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "Änderungssätze auswählen" -#: kallithea/controllers/compare.py:258 +#: kallithea/controllers/compare.py:261 msgid "Cannot compare repositories without using common ancestor" msgstr "" "Ohne einen gemeinsamen Vorfahren ist ein Vergleich der Repositories nicht" " möglich" #: kallithea/controllers/error.py:71 -#| msgid "revisions" msgid "No response" msgstr "Keine Rückmeldung" @@ -126,10 +116,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:335 -#: kallithea/templates/pullrequests/pullrequest_show.html:359 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "Der Änderungssatz war zu groß und wurde abgeschnitten..." @@ -147,103 +137,104 @@ msgid "There are no files yet. %s" msgstr "Es gibt hier noch keine Dateien. %s" -#: kallithea/controllers/files.py:194 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "%s auf %s" -#: kallithea/controllers/files.py:306 kallithea/controllers/files.py:366 -#: kallithea/controllers/files.py:433 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "Dieses Repository ist von %s am %s gesperrt worden" -#: kallithea/controllers/files.py:318 -msgid "You can only delete files with revision being a valid branch " +#: kallithea/controllers/files.py:319 +#, fuzzy +msgid "You can only delete files with revision being a valid branch" msgstr "Du kannst nur Dateien löschen, deren Revision ein gültiger Branch ist " -#: kallithea/controllers/files.py:329 +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "Datei %s via Kallithea gelöscht" -#: kallithea/controllers/files.py:351 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "Datei %s erfolgreich gelöscht" -#: kallithea/controllers/files.py:355 kallithea/controllers/files.py:421 -#: kallithea/controllers/files.py:502 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "Während des Commits trat ein Fehler auf" -#: kallithea/controllers/files.py:378 -msgid "You can only edit files with revision being a valid branch " -msgstr "" -"Du kannst nur Dateien bearbeiten, deren Revision ein gültiger Branch ist " - -#: kallithea/controllers/files.py:392 +#: kallithea/controllers/files.py:379 +#, fuzzy +msgid "You can only edit files with revision being a valid branch" +msgstr "Du kannst nur Dateien bearbeiten, deren Revision ein gültiger Branch ist " + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "Datei %s via Kallithea editiert" -#: kallithea/controllers/files.py:408 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Keine Änderungen" -#: kallithea/controllers/files.py:417 kallithea/controllers/files.py:491 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "Der Commit zu %s war erfolgreich" -#: kallithea/controllers/files.py:444 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Datei via Kallithea hinzugefügt" -#: kallithea/controllers/files.py:465 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Kein Inhalt" -#: kallithea/controllers/files.py:469 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "Kein Dateiname" -#: kallithea/controllers/files.py:494 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "Der Ort muss ein relativer Pfad sein und darf nicht .. enthalten" -#: kallithea/controllers/files.py:527 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "Downloads gesperrt" -#: kallithea/controllers/files.py:538 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Unbekannte Revision %s" -#: kallithea/controllers/files.py:540 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Leeres Repository" -#: kallithea/controllers/files.py:542 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "Unbekannter Archivtyp" -#: kallithea/controllers/files.py:772 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Änderungssätze" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:821 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "Entwicklungszweige" -#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:832 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Tags" @@ -257,31 +248,35 @@ msgid "Groups" msgstr "Gruppen" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 #: kallithea/templates/admin/repos/repos.html:9 #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 -#: kallithea/templates/base/base.html:131 -#: kallithea/templates/base/base.html:397 -#: kallithea/templates/base/base.html:569 +#: kallithea/templates/base/base.html:124 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Repositories" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "Zweig" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "Geschlossene Branches" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "Marke" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "Lesezeichen" @@ -292,98 +287,100 @@ msgstr "Öffentliches Logbuch" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:229 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "Logbuch" -#: kallithea/controllers/login.py:150 kallithea/controllers/login.py:196 -#| msgid "bad captcha" +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "Falsches Captcha" -#: kallithea/controllers/login.py:156 -msgid "You have successfully registered into Kallithea" -msgstr "Sie haben sich erfolgreich bei Kallithea registriert" - -#: kallithea/controllers/login.py:201 -msgid "Your password reset link was sent" -msgstr "Ihr Passwort Zurücksetzen link wurde versendet" - -#: kallithea/controllers/login.py:222 -msgid "" -"Your password reset was successful, new password has been sent to your " -"email" -msgstr "" -"Das Zurücksetzen des Passworted war erfolgreich, ein neues Passwort wurde" -" an ihre EMail Addresse gesendet" - -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "Sie haben sich erfolgreich bei %s registriert" + +#: kallithea/controllers/login.py:195 +#, fuzzy +msgid "A password reset confirmation code has been sent" +msgstr "Ihr Link um das Passwort zurückzusetzen wurde versendet" + +#: kallithea/controllers/login.py:244 +#, fuzzy +msgid "Invalid password reset token" +msgstr "Link zum Zurücksetzen des Passworts" + +#: kallithea/controllers/login.py:249 +#: kallithea/controllers/admin/my_account.py:167 +msgid "Successfully updated password" +msgstr "Erfolgreich Kennwort geändert" + +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (geschlossen)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "Änderungssatz" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "Spezial" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" -msgstr "" - -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:827 +msgstr "Branches anderer" + +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Lesezeichen" -#: kallithea/controllers/pullrequests.py:306 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "Fehler beim Erstellen des Pull-Requests: %s" -#: kallithea/controllers/pullrequests.py:352 -#: kallithea/controllers/pullrequests.py:499 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "Keine Beschreibung" -#: kallithea/controllers/pullrequests.py:359 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "Es wurde erfolgreich ein neuer Pullrequest eröffnet" -#: kallithea/controllers/pullrequests.py:362 -#: kallithea/controllers/pullrequests.py:449 -#: kallithea/controllers/pullrequests.py:504 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:365 -#: kallithea/controllers/pullrequests.py:452 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "Während des Erstellens des Pull Requests trat ein Fehler auf" -#: kallithea/controllers/pullrequests.py:397 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "Fehlende Changesets seit letztem Pull Request:" -#: kallithea/controllers/pullrequests.py:404 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "Neue Changesets in %s %s seit dem letzten Pull Request:" -#: kallithea/controllers/pullrequests.py:411 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" -msgstr "" - -#: kallithea/controllers/pullrequests.py:418 +msgstr "Vorgänger unverändert - zeige Diff zu lezter Version:" + +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " @@ -392,62 +389,67 @@ "Dieser Pull Request basiert auf einer anderen %s Revision. Daher ist kein" " Simple Diff verfügbar." -#: kallithea/controllers/pullrequests.py:420 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "Keine Änderungen seit der letzten Version gefunden in %s %s." -#: kallithea/controllers/pullrequests.py:458 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "Geschlossen, ersetzt durch %s." -#: kallithea/controllers/pullrequests.py:466 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "Pull Request Update erstellt" -#: kallithea/controllers/pullrequests.py:508 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "Pull Request aktualisiert" -#: kallithea/controllers/pullrequests.py:523 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "Erfolgreich Pull-Request gelöscht" -#: kallithea/controllers/pullrequests.py:582 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "Dieser Pull Request wurde bereits in %s integriert." -#: kallithea/controllers/pullrequests.py:584 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" "Dieser Pull Request wurde geschlossen und kann daher nicht aktualisiert " "werden." -#: kallithea/controllers/pullrequests.py:602 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "Dieser Pull Request kann mit Änderungen in %s aktualisiert werden:" - -#: kallithea/controllers/pullrequests.py:605 +#: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "Die folgenden Änderungen sind verfügbar unter %s:" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "Keine Changesets gefunden, um den Pull Request zu aktualisieren." -#: kallithea/controllers/pullrequests.py:613 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "Hinweis: Branch %s hat einen anderen Head: %s." -#: kallithea/controllers/pullrequests.py:619 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "Git Pull Request unterstützen bisher keine Updates." -#: kallithea/controllers/pullrequests.py:710 +#: kallithea/controllers/pullrequests.py:727 msgid "No permission to change pull request status" -msgstr "" - -#: kallithea/controllers/pullrequests.py:715 +msgstr "Keine Berechtigung zum Ändern des Pull Request Status" + +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "Erfolgreich Pull-Request gelöscht" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "Schließen." @@ -463,12 +465,12 @@ msgid "An error occurred during search operation." msgstr "Während der Suchoperation trat ein Fehler auf." -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "Es stehen noch keine Daten zur Verfügung" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "Statistiken sind deaktiviert für dieses Repository" @@ -489,71 +491,70 @@ msgid "Error occurred during update of defaults" msgstr "Ein Fehler trat beim updaten der Standardeinstellungen auf" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 -#: kallithea/controllers/admin/users.py:283 -#| msgid "forever" +#: kallithea/controllers/admin/users.py:284 msgid "Forever" msgstr "Immer" +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/users.py:285 +msgid "5 minutes" +msgstr "5 Minuten" + #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 -#: kallithea/controllers/admin/users.py:284 -msgid "5 minutes" -msgstr "5 Minuten" +#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/users.py:286 +msgid "1 hour" +msgstr "1 Stunde" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 -#: kallithea/controllers/admin/users.py:285 -msgid "1 hour" -msgstr "1 Stunde" +#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/users.py:287 +msgid "1 day" +msgstr "1 Tag" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 -#: kallithea/controllers/admin/users.py:286 -msgid "1 day" -msgstr "1 Tag" - -#: kallithea/controllers/admin/gists.py:63 #: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:287 +#: kallithea/controllers/admin/users.py:288 msgid "1 month" msgstr "1 Monat" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:289 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "Lebenszeit" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Ein fehler trat auf bei der Erstellung des gist" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "gist %s gelöscht" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "Ungeändert" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "Erfolgreich Kerninhalt aktualisiert" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "Erfolgreich Kerndaten aktualisiert" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "Fehler beim Aktualisieren der Kerndaten %s" -#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:208 -#: kallithea/model/user.py:230 +#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215 +#: kallithea/model/user.py:237 msgid "You can't edit this user since it's crucial for entire application" msgstr "" "Sie können diesen Benutzer nicht editieren, da er von entscheidender " @@ -564,47 +565,43 @@ msgstr "Ihr Account wurde erfolgreich aktualisiert" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:204 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "Fehler beim Aktualisieren der Benutzer %s" -#: kallithea/controllers/admin/my_account.py:167 -msgid "Successfully updated password" -msgstr "Erfolgreich Kennwort geändert" - #: kallithea/controllers/admin/my_account.py:178 msgid "Error occurred during update of user password" msgstr "Fehler bei der Änderung des Kennworts" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:413 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "Die EMail Addresse %s wurde zum Benutzer hinzugefügt" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:419 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "Währen der Speicherung der EMail Addresse trat ein Fehler auf" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:431 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "Die EMail Addresse wurde vom Benutzer entfernt" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:306 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "API Key wurde erfolgreich erstellt" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:319 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "API-Schlüssel erfolgreich zurückgesetzt" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:323 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "API-Schlüssel erfolgreich gelöscht" @@ -654,10 +651,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 -#: kallithea/templates/base/base.html:266 -#: kallithea/templates/base/base.html:267 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "Admin" @@ -688,7 +685,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "Manuelle Aktivierung externen Kontos" @@ -700,7 +697,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "Automatische Aktivierung externen Kontos" @@ -721,201 +718,198 @@ msgid "Error occurred during update of permissions" msgstr "Fehler bei der Änderung der globalen Berechtigungen" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "Fehler bei der Erstellung der Repositoriumsgruppe %s" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "Repositoriumsgruppe %s erstellt" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "Repositoriumsgruppe %s aktualisiert" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "Fehler bei der Aktualisierung der Repositoriumsgruppe %s" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "Die Gruppe enthält %s Repositorys und kann nicht gelöscht werden" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "Diese Gruppe enthält %s Untergruppen und kann nicht gelöscht werden" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "Repositoriumsgruppe %s entfernt" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "Fehler beim Löschen der Repositoriumsgruppe %s" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "Als Administrator kann man sich keine Berechtigungen entziehen" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "Berechtigungen der Repositoriumsgruppe aktualisiert" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "Fehler beim Entzug der Berechtigungen" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "Fehler beim Erstellen des Repositoriums %s" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "Repositorium %s von %s erstellt" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "Aufgespaltenes Repositorium %s zu %s" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "Repositorium erzeugt %s" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "Repository %s wurde erfolgreich aktualisiert" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "Fehler bei der Aktualisierung des Repositoriums %s" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "%s Spaltung abgetrennt" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "%s Spaltung gelöscht" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "Repositorium %s gelöscht" -#: kallithea/controllers/admin/repos.py:321 -#, python-format -#| msgid "Cannot delete %s it still contains attached forks" +#: kallithea/controllers/admin/repos.py:320 +#, python-format msgid "Cannot delete repository %s which still has forks" msgstr "%s konnte nicht gelöscht werden, da es noch Forks besitzt" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "Beim Löschen von %s trat ein Fehler auf" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "Repositoriumsberechtigungen aktualisiert" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "Fehler während der Erzeugung des Feldes" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "Fehler beim Entfernen des Feldes" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "-- Keine Abspaltung --" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "Sichtbarkeit des Repositorys im Öffentlichen Logbuch aktualisiert" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "" "Es trat ein Fehler während der Aktualisierung der Sicherbarkeit dieses " "Repositorys im Öffentlichen Logbuch auf" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "Nichts" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "Markiere Repository %s als Abzweig von Repository %s" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "Während dieser operation trat ein Fehler auf" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 -#| msgid "Repository is not locked" +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 msgid "Repository has been locked" msgstr "Repository wurde gesperrt" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 -#| msgid "Repository is not locked" +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 msgid "Repository has been unlocked" msgstr "Repository nicht mehr gesperrt" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "Fehler beim Entsperren" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "Cache Entfernung war erfolgreich" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "Währen der Cache Invalidierung trat ein Fehler auf" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "Von entferntem Ort übertragen" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "" "Es trat ein Fehler auf während das Repository von einem Entfernten " "Speicherort übertragen wurde" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "Während des löschens der Repository Statistiken trat ein Fehler auf" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "VCS-Einstellungen aktualisiert" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" @@ -923,55 +917,55 @@ "hgsubversion-Unterstützung konnte nicht aktiviert werden. Die " "\"hgsubversion\"-Bibliothek fehlt" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:274 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" "Ein Fehler ist während der Aktualisierung der Applikationseinstellungen " "aufgetreten" -#: kallithea/controllers/admin/settings.py:213 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" "Die Repositories wurden erfolgreich überprüft. Hinzugefügt: %s. Entfernt:" " %s." -#: kallithea/controllers/admin/settings.py:270 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "Anwendungseinstellungen aktualisiert" -#: kallithea/controllers/admin/settings.py:327 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "Visualisierungseinstellungen aktualisiert" -#: kallithea/controllers/admin/settings.py:332 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" "Es ist ein Fehler während der Aktualisierung der Layouteinstellung " "aufgetreten" -#: kallithea/controllers/admin/settings.py:358 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "Bitte gebe eine E-Mailadresse an" -#: kallithea/controllers/admin/settings.py:373 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "Task zum Versenden von E-Mails erstellt" -#: kallithea/controllers/admin/settings.py:404 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "Neuer Hook hinzugefügt" -#: kallithea/controllers/admin/settings.py:418 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "Die Hooks wurden aktutalisiert" +#: kallithea/controllers/admin/settings.py:396 +msgid "Error occurred during hook creation" +msgstr "Während der Erzeugung des Hooks ist ein Fehler aufgetreten" + #: kallithea/controllers/admin/settings.py:422 -msgid "Error occurred during hook creation" -msgstr "Während der Erzeugung des Hooks ist ein Fehler aufgetreten" - -#: kallithea/controllers/admin/settings.py:448 msgid "Whoosh reindex task scheduled" msgstr "Whoosh Reindizierungs Aufgabe wurde zur Ausführung geplant" @@ -1012,12 +1006,12 @@ msgstr "Berechtigungen der Benutzergruppe wurden aktualisiert" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:382 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "Berechtigungen wurden aktualisiert" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:386 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "Es ist ein Fehler während des Speicherns der Berechtigungen aufgetreten" @@ -1031,57 +1025,61 @@ msgid "Error occurred during creation of user %s" msgstr "Während des Erstellens des Benutzers %s ist ein Fehler aufgetreten" -#: kallithea/controllers/admin/users.py:184 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "Der Benutzer wurde erfolgreich aktualisiert" -#: kallithea/controllers/admin/users.py:220 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "Der Nutzer wurde erfolgreich gelöscht" -#: kallithea/controllers/admin/users.py:225 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "Während der Löschen des Benutzers trat ein Fehler auf" -#: kallithea/controllers/admin/users.py:238 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "Der Standard-Benutzer kann nicht bearbeitet werden" -#: kallithea/controllers/admin/users.py:461 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "Die IP-Adresse %s wurde zur Nutzerwhitelist hinzugefügt" -#: kallithea/controllers/admin/users.py:467 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "Während des Speicherns der IP-Adresse ist ein Fehler aufgetreten" -#: kallithea/controllers/admin/users.py:481 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "IP-Adresse wurde von der Nutzerwhitelist entfernt" -#: kallithea/lib/auth.py:744 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "IP-Adresse %s ist nicht erlaubt" -#: kallithea/lib/auth.py:757 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "Ungültiger API Key" -#: kallithea/lib/auth.py:795 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "Sie müssen ein Registrierter Nutzer sein um diese Aktion durchzuführen" -#: kallithea/lib/auth.py:827 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "Sie müssen sich anmelden um diese Seite aufzurufen" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "Das Repository konnte nicht im Filesystem gefunden werden" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "Änderungssatz nicht gefunden" @@ -1099,127 +1097,125 @@ msgid "No changes detected" msgstr "Keine Änderungen erkannt" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "Branch %s gelöscht" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "Tag %s erstellt" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "Zeige alle Kombinierten Änderungensätze %s->%s" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "Vergleichsansicht" -#: kallithea/lib/helpers.py:696 +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "und" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "%s mehr" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "revisionen" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, fuzzy, python-format -#| msgid "fork name %s" msgid "Fork name %s" msgstr "Fork Name %s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, fuzzy, python-format -#| msgid "Pull request #%s" msgid "Pull request %s" msgstr "Pull Request #%s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "[gelöscht] Repository" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "[erstellt] Repository" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "[erstellt] Repository als Fork" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "[forked] Repository" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "[aktualisiert] Repository" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "Archiv von Repository [heruntergeladen]" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "Repository [gelöscht]" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "Benutzer [erstellt]" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "Benutzer [akutalisiert]" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "Benutzergruppe [erstellt]" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "Benutzergruppe [aktualisiert]" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "Revision [kommentiert] in Repository" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "Pull Request [kommentiert] für" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "Pull Request [geschlossen] für" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[Pushed] in" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "[via Kallithea] in Repository [committed]" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "[Pulled von Remote] in Repository" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "[Pulled] von" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "[Following gestartet] für Repository" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "[Following gestoppt] für Repository" @@ -1229,8 +1225,8 @@ msgstr " und %s weitere" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:322 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "Keine Dateien" @@ -1254,7 +1250,7 @@ msgid "chmod" msgstr "chmod" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1265,69 +1261,69 @@ "es im Dateisystem erstellt oder umbenannt. Bitte starten sie die " "Applikation erneut um die Repositories neu zu Indizieren" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "%d Jahr" msgstr[1] "%d Jahre" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d Monat" msgstr[1] "%d Monate" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d Tag" msgstr[1] "%d Tage" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d Stunde" msgstr[1] "%d Stunden" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d Minute" msgstr[1] "%d Minuten" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "%d Sekunde" msgstr[1] "%d Sekunden" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "in %s" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "vor %s" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "in %s und %s" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "%s und %s her" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "jetzt gerade" @@ -1426,7 +1422,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Kallithea Administrator" @@ -1537,7 +1533,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "Akzeptiert" @@ -1552,7 +1548,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "Abgelehnt" @@ -1579,7 +1575,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "höchste Ebene" @@ -1726,7 +1722,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "Registrierung deaktiviert" @@ -1753,14 +1749,14 @@ msgstr "Benutzerregistrierung mit automatische Kontoaktivierung" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" "Erstellung von Repositories mit Schreibzugriff für Repositorygruppe " "aktiviert" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" "Erstellung von Repositories mit Schreibzugriff für Repositorygruppe " @@ -1771,125 +1767,111 @@ msgid "on line %s" msgstr "in Zeile %s" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[Mention]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Repositories" -#: kallithea/model/db.py:1668 -#| msgid "Unauthorized access to resource" -msgid "Default user has read access to new repositories" -msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repositories" - -#: kallithea/model/db.py:1669 -#| msgid "Unauthorized access to resource" -msgid "Default user has write access to new repositories" -msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Repositories" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Repositories" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" -msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Repository-Gruppen" +msgid "Default user has read access to new repositories" +msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repositories" #: kallithea/model/db.py:1673 +msgid "Default user has write access to new repositories" +msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Repositories" + +#: kallithea/model/db.py:1674 +msgid "Default user has admin access to new repositories" +msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Repositories" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" +msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Repository-Gruppen" + +#: kallithea/model/db.py:1677 msgid "Default user has read access to new repository groups" msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repository-Gruppen" -#: kallithea/model/db.py:1674 +#: kallithea/model/db.py:1678 msgid "Default user has write access to new repository groups" msgstr "Der Standard-Benutzer Schreibrechte auf neuen Repository-Gruppen" -#: kallithea/model/db.py:1675 +#: kallithea/model/db.py:1679 msgid "Default user has admin access to new repository groups" msgstr "Der Standard-Benutzer Admin-Rechte auf neuen Repository-Gruppen" -#: kallithea/model/db.py:1677 +#: kallithea/model/db.py:1681 msgid "Default user has no access to new user groups" msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Benutzer-Gruppen" -#: kallithea/model/db.py:1678 +#: kallithea/model/db.py:1682 msgid "Default user has read access to new user groups" msgstr "Der Standard-Benutzer hat Leserechte auf neuen Benutzer-Gruppen" -#: kallithea/model/db.py:1679 +#: kallithea/model/db.py:1683 msgid "Default user has write access to new user groups" msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Benutzer-Gruppen" -#: kallithea/model/db.py:1680 +#: kallithea/model/db.py:1684 msgid "Default user has admin access to new user groups" msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Benutzer-Gruppen" -#: kallithea/model/db.py:1682 -#, python-format -#| msgid "Created repository group %s" +#: kallithea/model/db.py:1686 msgid "Only admins can create repository groups" msgstr "Nur Admins können Repository-Gruppen erstellen" -#: kallithea/model/db.py:1683 -#, fuzzy, python-format -#| msgid "Created repository group %s" +#: kallithea/model/db.py:1687 +#, fuzzy msgid "Non-admins can create repository groups" msgstr "Nicht-Admins können Repository-Gruppen erstellen" -#: kallithea/model/db.py:1685 -#| msgid "[created] user group" +#: kallithea/model/db.py:1689 msgid "Only admins can create user groups" msgstr "Nur Admins können Benutzer-Gruppen erstellen" -#: kallithea/model/db.py:1686 -#| msgid "[created] user group" +#: kallithea/model/db.py:1690 msgid "Non-admins can create user groups" msgstr "Nicht-Admins können Benutzer-Gruppen erstellen" -#: kallithea/model/db.py:1688 +#: kallithea/model/db.py:1692 #, fuzzy -#| msgid "Top level repositories" msgid "Only admins can create top level repositories" msgstr "Repositories oberster Ebene" -#: kallithea/model/db.py:1689 +#: kallithea/model/db.py:1693 #, fuzzy -#| msgid "Top level repositories" msgid "Non-admins can create top level repositories" msgstr "Repositories oberster Ebene" -#: kallithea/model/db.py:1694 -#| msgid "Location of repositories" +#: kallithea/model/db.py:1698 msgid "Only admins can fork repositories" msgstr "Nur Admins können Repositories forken" -#: kallithea/model/db.py:1695 -#| msgid "Location of repositories" -msgid "Non-admins can can fork repositories" -msgstr "Nicht-Admins können Repositories forken" - -#: kallithea/model/db.py:1698 -#, fuzzy -#| msgid "User Registration with manual account activation" -msgid "User registration with manual account activation" -msgstr "Benutzerregistrierung mit manueller Kontoaktivierung" - #: kallithea/model/db.py:1699 #, fuzzy -#| msgid "User Registration with automatic account activation" +msgid "Non-admins can fork repositories" +msgstr "Nicht-Admins können Repositories forken" + +#: kallithea/model/db.py:1702 +#, fuzzy +msgid "User registration with manual account activation" +msgstr "Benutzerregistrierung mit manueller Kontoaktivierung" + +#: kallithea/model/db.py:1703 +#, fuzzy msgid "User registration with automatic account activation" msgstr "Benutzerregistrierung mit automatische Kontoaktivierung" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy -#| msgid "Not Reviewed" msgid "Not reviewed" msgstr "Nicht Begutachtet" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy -#| msgid "Under Review" msgid "Under review" msgstr "In Begutachtung" @@ -1911,43 +1893,37 @@ msgid "Enter %(min)i characters or more" msgstr "Bitte mindestens %(min)i Zeichen eingeben" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "Name darf nicht nur Ziffern enthalten" #: kallithea/model/notification.py:254 #, fuzzy, python-format -#| msgid "%(user)s commented on changeset at %(when)s" msgid "%(user)s commented on changeset %(age)s" msgstr "%(user)s hat am %(when)s ein Changeset kommentiert" #: kallithea/model/notification.py:255 #, fuzzy, python-format -#| msgid "%(user)s sent message at %(when)s" msgid "%(user)s sent message %(age)s" msgstr "%(user)s hat am %(when)s eine Nachricht gesendet" #: kallithea/model/notification.py:256 #, fuzzy, python-format -#| msgid "%(user)s mentioned you at %(when)s" msgid "%(user)s mentioned you %(age)s" msgstr "%(user)s hat Sie am %(when)s erwähnt" #: kallithea/model/notification.py:257 #, fuzzy, python-format -#| msgid "%(user)s registered in Kallithea at %(when)s" msgid "%(user)s registered in Kallithea %(age)s" msgstr "%(user)s hat sich am %(when)s bei Kallithea registriert" #: kallithea/model/notification.py:258 #, fuzzy, python-format -#| msgid "%(user)s opened new pull request at %(when)s" msgid "%(user)s opened new pull request %(age)s" msgstr "%(user)s hat am %(when)s einen neuen Pull Request eröffnet" #: kallithea/model/notification.py:259 #, fuzzy, python-format -#| msgid "%(user)s commented on pull request at %(when)s" msgid "%(user)s commented on pull request %(age)s" msgstr "%(user)s hat am %(when)s einen Pull Request kommentiert" @@ -1983,11 +1959,7 @@ #: kallithea/model/notification.py:302 #, fuzzy, python-format -#| msgid "" "Comment on %(repo_name)s changeset %(short_id)s on %(branch)s by " -#| "%(comment_username)s" -msgid "" -"[Comment from %(comment_username)s] %(repo_name)s changeset %(short_id)s " -"on %(branch)s" +msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s" msgstr "" "Kommentar für %(repo_name)s Changeset %(short_id)s in %(branch)s erstellt" " von %(comment_username)s" @@ -1999,21 +1971,14 @@ #: kallithea/model/notification.py:307 #, fuzzy, python-format -#| msgid "" " -msgid "" -"[Added by %(pr_username)s] %(repo_name)s pull request %(pr_nice_id)s from" -" %(ref)s" +msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "" "Review Request für %(repo_name)s Pull Request #%(pr_id)s von %(ref)s " "erstellt von %(pr_username)s" #: kallithea/model/notification.py:308 #, fuzzy, python-format -#| msgid "" "Comment on %(repo_name)s pull request #%(pr_id)s from %(ref)s by " -#| "%(comment_username)s" -msgid "" -"[Comment from %(comment_username)s] %(repo_name)s pull request " -"%(pr_nice_id)s from %(ref)s" +msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "" "Kommentar von %(comment_username)s für %(repo_name)s Pull Request " "#%(pr_id)s von %(ref)s" @@ -2024,27 +1989,25 @@ #: kallithea/model/pull_request.py:137 #, fuzzy, python-format -#| msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s" msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "%(user)s möchte ein Review des Pull Request #%(pr_id)s: %(pr_title)s" -#: kallithea/model/scm.py:813 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "Letzter Tip" -#: kallithea/model/user.py:185 +#: kallithea/model/user.py:192 msgid "New user registration" msgstr "Neue Benutzerregistrierung" -#: kallithea/model/user.py:249 +#: kallithea/model/user.py:256 #, fuzzy -#| msgid "You can't remove this user since it's crucial for entire application" msgid "You can't remove this user since it is crucial for the entire application" msgstr "" "Sie können diesen Benutzer nicht löschen, da er von entscheidender " "Bedeutung für die gesamte Applikation ist" -#: kallithea/model/user.py:254 +#: kallithea/model/user.py:261 #, python-format msgid "" "User \"%s\" still owns %s repositories and cannot be removed. Switch " @@ -2054,7 +2017,7 @@ "nicht entfernt werden. Entweder muss der Besitzer geändert oder das " "Repository entfernt werden: %s" -#: kallithea/model/user.py:259 +#: kallithea/model/user.py:266 #, python-format msgid "" "User \"%s\" still owns %s repository groups and cannot be removed. Switch" @@ -2064,7 +2027,7 @@ "kann daher nicht entfernt werden. Entweder muss der Besitzer geändert " "oder die Repositorygruppen müssen entfernt werden: %s" -#: kallithea/model/user.py:266 +#: kallithea/model/user.py:273 #, python-format msgid "" "User \"%s\" still owns %s user groups and cannot be removed. Switch " @@ -2074,37 +2037,38 @@ "nicht entfernt werden. Entweder muss der Besitzer geändert oder die " "Benutzergruppen müssen gelöscht werden: %s" -#: kallithea/model/user.py:296 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "Link zum Zurücksetzen des Passworts" -#: kallithea/model/user.py:319 -msgid "Your new password" -msgstr "Dein neues Passwort" - -#: kallithea/model/user.py:320 -#, python-format -msgid "Your new Kallithea password:%s" -msgstr "Ihr neues Kallithea-Passwort: %s" +#: kallithea/model/user.py:418 +#, fuzzy +msgid "Password reset notification" +msgstr "Link zum Zurücksetzen des Passworts" + +#: kallithea/model/user.py:419 +#, python-format +msgid "" +"The password to your account %s has been changed using password reset " +"form." +msgstr "" #: kallithea/model/validators.py:77 kallithea/model/validators.py:78 msgid "Value cannot be an empty list" msgstr "Eine leere Liste ist kein gültiger Wert" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "Benutezrname \"%(username)s\" existiert bereits" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, fuzzy, python-format -#| msgid "Username %(username)s is not valid" msgid "Username \"%(username)s\" cannot be used" msgstr "Benutzername \"%(username)s\" ist ungültig" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 #, fuzzy -#| msgid "" " msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" @@ -2113,25 +2077,25 @@ "oder Bindestriche enthalten und muss mit einem alphanumerischen Zeichen " "oder einem Unterstrich beginnen" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "Die Eingabe ist nicht gültig" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "Benutzername \"%(username)s\" ist ungültig" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "Ungültiger Benutzergruppenname" -#: kallithea/model/validators.py:153 +#: kallithea/model/validators.py:155 #, python-format msgid "User group \"%(usergroup)s\" already exists" msgstr "Benutzergruppe \"%(usergroup)s\" existiert bereits" -#: kallithea/model/validators.py:155 +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" @@ -2140,116 +2104,111 @@ "Unterstriche, Punkte oder Bindestriche enthalten und muss mit einem " "alphanumerischen Zeichen beginnen" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "Kann diese Gruppe nicht als vorgesetzt setzen" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "Gruppe \"%(group_name)s\" existiert bereits" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "Es gibt bereits ein Repository mit \"%(group_name)s\"" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "Üngültige(nicht ASCII) Zeichen im Passwort" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "Ungültiges altes Passwort" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "Die Passwörter stimmen nicht überein" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 #, fuzzy -#| msgid "invalid password" msgid "Invalid username or password" msgstr "Ungültiges Passwort" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "Schlüssel stimmt nicht überein" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, fuzzy, python-format -#| msgid "Repository name %(repo)s is disallowed" msgid "Repository name %(repo)s is not allowed" msgstr "Repository Name \"%(repo)s\" ist verboten" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "Es gibt bereits ein Repository mit \"%(repo)s\"" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "" "Es gibt bereits ein Repository mit \"%(repo)s\" in der Gruppe " "\"%(group)s\"" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "Eine Repositorygruppe mit dem Namen \"%(repo)s\" existiert bereits" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy -#| msgid "private repository" msgid "Invalid repository URL" msgstr "privates Repository" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "Forke um den selben typ wie der Vorgesetze zu haben" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "" "Du hast nicht die erforderlichen Berechtigungen, um in dieser Gruppe ein " "Repository zu erzeugen" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "keine Berechtigung, um ein Repository auf höchster Ebene anzulegen" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "Sie haben keine Berechtigung, um an diesem Ort ein Repository anzulegen" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "Dieser Benutzername oder Benutzergruppenname ist nicht gültig" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "Dies ist ein Ungültiger Pfad" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 #, fuzzy -#| msgid "This email address is already taken" msgid "This email address is already in use" msgstr "Diese E-Mailaddresse ist bereits in Benutzung" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, fuzzy, python-format -#| msgid "email \"%(email)s\" does not exist." msgid "Email address \"%(email)s\" not found" msgstr "E-MailAddresse \"%(email)s\" existiert nicht." -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" @@ -2257,28 +2216,28 @@ "Das LDAP-Login-Attribut des CN muss angeben werden - Es ist der Name des " "Attributes äquivalent zu \"Benutzername\"" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "Bitte eine gültige IPv4- oder IPv6-Adresse angeben" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" "Die Größe (in Bits) des Netzwerks muss im Bereich 0-32 liegen (nicht " "%(bits)r)" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" "Der Name eines Schlüssels darf nur aus Buchstaben, Ziffern, Unterstrich " "und Bindestrich bestehen" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "Dateiname darf kein Unterverzeichnis enthalten" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2405,7 +2364,9 @@ #: kallithea/templates/admin/repos/repos.html:50 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8 #: kallithea/templates/admin/user_groups/user_groups.html:50 -#: kallithea/templates/pullrequests/pullrequest_show.html:229 +#: kallithea/templates/pullrequests/pullrequest_data.html:16 +#: kallithea/templates/pullrequests/pullrequest_show.html:156 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "Besitzer" @@ -2413,7 +2374,7 @@ #: kallithea/templates/index_base.html:140 #: kallithea/templates/admin/my_account/my_account_repos.html:57 #: kallithea/templates/admin/my_account/my_account_watched.html:57 -#: kallithea/templates/base/root.html:44 +#: kallithea/templates/base/root.html:43 #: kallithea/templates/bookmarks/bookmarks.html:79 #: kallithea/templates/branches/branches.html:79 #: kallithea/templates/journal/journal.html:198 @@ -2425,7 +2386,7 @@ #: kallithea/templates/index_base.html:141 #: kallithea/templates/admin/my_account/my_account_repos.html:58 #: kallithea/templates/admin/my_account/my_account_watched.html:58 -#: kallithea/templates/base/root.html:45 +#: kallithea/templates/base/root.html:44 #: kallithea/templates/bookmarks/bookmarks.html:80 #: kallithea/templates/branches/branches.html:80 #: kallithea/templates/journal/journal.html:199 @@ -2441,7 +2402,7 @@ #: kallithea/templates/index_base.html:143 #: kallithea/templates/admin/my_account/my_account_repos.html:60 #: kallithea/templates/admin/my_account/my_account_watched.html:60 -#: kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:46 #: kallithea/templates/bookmarks/bookmarks.html:82 #: kallithea/templates/branches/branches.html:82 #: kallithea/templates/journal/journal.html:201 @@ -2453,7 +2414,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:147 kallithea/templates/base/root.html:48 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2463,7 +2424,7 @@ msgstr "Lade..." #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:333 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "Log In" @@ -2478,14 +2439,15 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:309 +#: kallithea/templates/base/base.html:390 +#: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "Benutzername" #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:318 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "Passwort" @@ -2497,7 +2459,7 @@ msgid "Forgot your password ?" msgstr "Passowrt Vergessen?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:329 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "Kein Account?" @@ -2510,11 +2472,14 @@ msgstr "Passwort zurücksetzen" #: kallithea/templates/password_reset.html:12 +#: kallithea/templates/password_reset_confirmation.html:12 #, python-format msgid "Reset Your Password to %s" msgstr "Setze dein Passwort auf %s zurück" #: kallithea/templates/password_reset.html:14 +#: kallithea/templates/password_reset_confirmation.html:5 +#: kallithea/templates/password_reset_confirmation.html:14 msgid "Reset Your Password" msgstr "Setze dein Passwort zurück" @@ -2532,13 +2497,43 @@ msgstr "E-Mail zum Zurücksetzen des Passworts anfordern" #: kallithea/templates/password_reset.html:47 +#, fuzzy msgid "" -"Password reset link will be sent to the email address matching your " -"username." +"A password reset link will be sent to the specified email address if it " +"is registered in the system." msgstr "" "Der Link zum Zurücksetzen des Passworts wird an die zum Benutzernamen " "zugehörige E-Mailaddresse gesendet." +#: kallithea/templates/password_reset_confirmation.html:19 +#, python-format +msgid "You are about to set a new password for the email address %s." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:20 +msgid "" +"Note that you must use the same browser session for this as the one used " +"to request the password reset." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:30 +msgid "Code you received in the email" +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:39 +#, fuzzy +msgid "New Password" +msgstr "Neues Passwort" + +#: kallithea/templates/password_reset_confirmation.html:48 +#, fuzzy +msgid "Confirm New Password" +msgstr "Bestätige neues Passwort" + +#: kallithea/templates/password_reset_confirmation.html:56 +msgid "Confirm" +msgstr "Bestätigen" + #: kallithea/templates/register.html:5 kallithea/templates/register.html:14 #: kallithea/templates/register.html:90 msgid "Sign Up" @@ -2592,10 +2587,6 @@ msgid "There are no branches yet" msgstr "Es gibt bisher keine Branches" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "Geschlossene Branches" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2684,7 +2675,6 @@ #: kallithea/templates/admin/auth/auth_settings.html:33 #, fuzzy -#| msgid "" " msgid "" "Comma-separated list of plugins; Kallithea will try user authentication " "in plugin order" @@ -2829,16 +2819,15 @@ #: kallithea/templates/admin/users/user_edit_api_keys.html:8 #: kallithea/templates/admin/users/user_edit_api_keys.html:27 #, fuzzy -#| msgid "never" msgid "Never" msgstr "nie" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "Gist aktualisieren" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "Abbrechen" @@ -2861,7 +2850,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:244 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "Neuen Gist erstellen" @@ -2949,7 +2938,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2969,8 +2959,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2997,13 +2985,12 @@ msgstr "erstellt" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:350 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "Mein Account" @@ -3014,7 +3001,6 @@ #: kallithea/templates/admin/my_account/my_account.html:36 #, fuzzy -#| msgid "New email address" msgid "Email Addresses" msgstr "Neue E-Mailadresse" @@ -3025,14 +3011,12 @@ #: kallithea/templates/admin/my_account/my_account.html:39 #, fuzzy -#| msgid "repositories" msgid "Owned Repositories" msgstr "Repositories" #: kallithea/templates/admin/my_account/my_account.html:40 #: kallithea/templates/journal/journal.html:53 #, fuzzy -#| msgid "repositories" msgid "Watched Repositories" msgstr "Repositories" @@ -3041,7 +3025,6 @@ #: kallithea/templates/admin/user_groups/user_group_edit.html:32 #: kallithea/templates/admin/users/user_edit.html:34 #, fuzzy -#| msgid "Copy permissions" msgid "Show Permissions" msgstr "Berechtigungen kopieren" @@ -3053,42 +3036,36 @@ #: kallithea/templates/admin/my_account/my_account_api_keys.html:14 #: kallithea/templates/admin/users/user_edit_api_keys.html:14 #, fuzzy, python-format -#| msgid "Confirm to reset this api key: %s" msgid "Confirm to reset this API key: %s" msgstr "Zurücksetzen des API Keys \"%s\" bestätigen" #: kallithea/templates/admin/my_account/my_account_api_keys.html:30 #: kallithea/templates/admin/users/user_edit_api_keys.html:30 #, fuzzy -#| msgid "expired" msgid "Expired" msgstr "verfallen" #: kallithea/templates/admin/my_account/my_account_api_keys.html:40 #: kallithea/templates/admin/users/user_edit_api_keys.html:40 #, fuzzy, python-format -#| msgid "Confirm to remove this api key: %s" msgid "Confirm to remove this API key: %s" msgstr "Entfernen des API Keys \"%s\" bestätigen" #: kallithea/templates/admin/my_account/my_account_api_keys.html:42 #: kallithea/templates/admin/users/user_edit_api_keys.html:42 #, fuzzy -#| msgid "remove" msgid "Remove" msgstr "entfernen" #: kallithea/templates/admin/my_account/my_account_api_keys.html:49 #: kallithea/templates/admin/users/user_edit_api_keys.html:49 #, fuzzy -#| msgid "No additional api keys specified" msgid "No additional API keys specified" msgstr "Keine weiteren API Keys spezifiziert" #: kallithea/templates/admin/my_account/my_account_api_keys.html:61 #: kallithea/templates/admin/users/user_edit_api_keys.html:61 #, fuzzy -#| msgid "New api key" msgid "New API key" msgstr "Neuer API Key" @@ -3167,7 +3144,6 @@ #: kallithea/templates/admin/my_account/my_account_profile.html:16 #: kallithea/templates/admin/users/user_edit_profile.html:15 #, fuzzy -#| msgid "current IP" msgid "Current IP" msgstr "Aktuelle IP-Adresse" @@ -3177,7 +3153,7 @@ #: kallithea/templates/admin/my_account/my_account_repos.html:59 #: kallithea/templates/admin/my_account/my_account_watched.html:59 -#: kallithea/templates/base/root.html:46 +#: kallithea/templates/base/root.html:45 #: kallithea/templates/bookmarks/bookmarks.html:81 #: kallithea/templates/branches/branches.html:81 #: kallithea/templates/journal/journal.html:200 @@ -3204,7 +3180,7 @@ msgstr "Kommentare" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:190 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "Pull Requests" @@ -3222,7 +3198,7 @@ msgstr "Zeige Benachrichtigung" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:349 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "Benachrichtigungen" @@ -3267,7 +3243,6 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:26 #, fuzzy -#| msgid "Import existing repository ?" msgid "Apply to all existing repositories" msgstr "Bestehendes Repository importieren?" @@ -3295,13 +3270,11 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:40 #, fuzzy -#| msgid "Import existing repository ?" msgid "Apply to all existing repository groups" msgstr "Bestehendes Repository importieren?" #: kallithea/templates/admin/permissions/permissions_globals.html:41 #, fuzzy -#| msgid "Copy permission set from parent repository group." msgid "Permissions for the Default user on new repository groups." msgstr "Rechte der übergeordneten Repositorygruppe kopieren." @@ -3312,9 +3285,6 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:53 #, fuzzy -#| msgid "" " -#| " " -#| " "will be lost" msgid "" "All default permissions on each user group will be reset to chosen " "permission, note that all custom default permission on user groups will " @@ -3326,7 +3296,7 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:54 msgid "Apply to all existing user groups" -msgstr "" +msgstr "Auf alle bestehenden Benutzergruppen anwenden" #: kallithea/templates/admin/permissions/permissions_globals.html:55 msgid "Permissions for the Default user on new user groups." @@ -3334,7 +3304,6 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:60 #, fuzzy -#| msgid "Repository creation" msgid "Top level repository creation" msgstr "Repository erstellung" @@ -3354,7 +3323,6 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:74 #, fuzzy -#| msgid "" " msgid "" "With this, write permission to a repository group allows creating " "repositories inside that group. Without this, group write permissions " @@ -3390,7 +3358,6 @@ #: kallithea/templates/admin/permissions/permissions_ips.html:13 #: kallithea/templates/admin/users/user_edit_ips.html:23 #, fuzzy, python-format -#| msgid "Confirm to delete this ip: %s" msgid "Confirm to delete this IP address: %s" msgstr "Bestätigen diese IP zu löschen: %s" @@ -3448,7 +3415,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:158 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3487,7 +3454,7 @@ #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7 #: kallithea/templates/admin/users/user_edit_advanced.html:8 -#: kallithea/templates/pullrequests/pullrequest_show.html:146 +#: kallithea/templates/pullrequests/pullrequest_show.html:148 msgid "Created on" msgstr "Erstellt am" @@ -3507,7 +3474,6 @@ #: kallithea/templates/admin/repos/repo_edit_permissions.html:12 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11 #, fuzzy -#| msgid "user/user group" msgid "User/User Group" msgstr "Benutzer/Benutzergruppe" @@ -3518,7 +3484,6 @@ #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45 #, fuzzy -#| msgid "default" msgid "Default" msgstr "standart" @@ -3529,7 +3494,6 @@ #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71 #, fuzzy -#| msgid "revoke" msgid "Revoke" msgstr "entziehen" @@ -3590,7 +3554,6 @@ #: kallithea/templates/admin/repos/repo_add_base.html:17 #, fuzzy -#| msgid "[created] repository" msgid "Clone remote repository" msgstr "[erstellt] Repository" @@ -3722,6 +3685,11 @@ msgid "Unlock Repository" msgstr "Repository entsperren" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "Sperren des Repositorys bestätigen." @@ -3778,10 +3746,6 @@ msgid "Invalidate Repository Cache" msgstr "Ungültiger Repositorycache" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -3846,25 +3810,21 @@ #: kallithea/templates/admin/repos/repo_edit_permissions.html:21 #, fuzzy -#| msgid "private repository" msgid "Private Repository" msgstr "privates Repository" #: kallithea/templates/admin/repos/repo_edit_remote.html:3 -#, fuzzy, python-format -#| msgid "Created repository %s" +#, fuzzy msgid "Remote repository URL" msgstr "Repositorium erzeugt %s" #: kallithea/templates/admin/repos/repo_edit_remote.html:9 #, fuzzy -#| msgid "[pulled from remote] into repository" msgid "Pull Changes from Remote Repository" msgstr "[Pulled von Remote] in Repository" #: kallithea/templates/admin/repos/repo_edit_remote.html:11 #, fuzzy -#| msgid "[pulled from remote] into repository" msgid "Confirm to pull changes from remote repository." msgstr "[Pulled von Remote] in Repository" @@ -3874,7 +3834,6 @@ #: kallithea/templates/admin/repos/repo_edit_settings.html:11 #, fuzzy -#| msgid "private repository" msgid "Permanent Repository ID" msgstr "privates Repository" @@ -3898,13 +3857,11 @@ #: kallithea/templates/admin/repos/repo_edit_settings.html:21 #, fuzzy -#| msgid "[created] repository" msgid "Remote repository" msgstr "[erstellt] Repository" #: kallithea/templates/admin/repos/repo_edit_settings.html:25 #, fuzzy -#| msgid "Repository" msgid "Repository URL" msgstr "Repository" @@ -4043,7 +4000,7 @@ msgid "Custom Hooks" msgstr "" -#: kallithea/templates/admin/settings/settings_hooks.html:68 +#: kallithea/templates/admin/settings/settings_hooks.html:67 msgid "Failed to remove hook" msgstr "" @@ -4079,7 +4036,18 @@ "Current hooks will be updated to the latest version." msgstr "" +#: kallithea/templates/admin/settings/settings_mapping.html:28 +msgid "Overwrite existing Git hooks" +msgstr "" + #: kallithea/templates/admin/settings/settings_mapping.html:30 +msgid "" +"If installing Git hooks, overwrite any existing hooks, even if they do " +"not seem to come from Kallithea. WARNING: This operation will destroy any" +" custom git hooks you may have deployed by hand!" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:35 msgid "Rescan Repositories" msgstr "" @@ -4107,7 +4075,6 @@ #: kallithea/templates/admin/settings/settings_system.html:4 #, fuzzy -#| msgid "check for updates" msgid "Check for updates" msgstr "Auf Updates prüfen" @@ -4310,7 +4277,6 @@ #: kallithea/templates/admin/settings/settings_visual.html:92 #, fuzzy -#| msgid "Settings" msgid "Meta Tagging" msgstr "Einstellungen" @@ -4352,7 +4318,6 @@ #: kallithea/templates/admin/user_groups/user_group_edit.html:33 #, fuzzy -#| msgid "members" msgid "Show Members" msgstr "mitglieder" @@ -4497,47 +4462,43 @@ msgid "Git repository" msgstr "Git Repository" -#: kallithea/templates/base/base.html:126 +#: kallithea/templates/base/base.html:119 msgid "Create Fork" msgstr "Fork erstellen" -#: kallithea/templates/base/base.html:137 +#: kallithea/templates/base/base.html:130 #: kallithea/templates/data_table/_dt_elements.html:13 #: kallithea/templates/data_table/_dt_elements.html:17 #: kallithea/templates/summary/summary.html:8 msgid "Summary" msgstr "Zusammenfassung" -#: kallithea/templates/base/base.html:139 -#: kallithea/templates/base/base.html:141 +#: kallithea/templates/base/base.html:132 +#: kallithea/templates/base/base.html:134 #: kallithea/templates/changelog/changelog.html:14 #: kallithea/templates/data_table/_dt_elements.html:21 #: kallithea/templates/data_table/_dt_elements.html:25 msgid "Changelog" msgstr "" -#: kallithea/templates/base/base.html:143 +#: kallithea/templates/base/base.html:136 #: kallithea/templates/data_table/_dt_elements.html:29 #: kallithea/templates/data_table/_dt_elements.html:33 #: kallithea/templates/files/files.html:11 msgid "Files" msgstr "Dateien" -#: kallithea/templates/base/base.html:145 -msgid "Switch To" -msgstr "" +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 +msgid "Options" +msgstr "Optionen" #: kallithea/templates/base/base.html:152 -#: kallithea/templates/base/base.html:154 -msgid "Options" -msgstr "Optionen" - -#: kallithea/templates/base/base.html:162 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "Fork vergleichen" -#: kallithea/templates/base/base.html:164 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4547,117 +4508,121 @@ msgid "Compare" msgstr "" -#: kallithea/templates/base/base.html:166 -#: kallithea/templates/base/base.html:254 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "" +#: kallithea/templates/base/base.html:160 +msgid "Unlock" +msgstr "" + +#: kallithea/templates/base/base.html:162 +msgid "Lock" +msgstr "" + #: kallithea/templates/base/base.html:170 -msgid "Unlock" -msgstr "" - -#: kallithea/templates/base/base.html:172 -msgid "Lock" -msgstr "" - -#: kallithea/templates/base/base.html:180 msgid "Follow" msgstr "" -#: kallithea/templates/base/base.html:181 +#: kallithea/templates/base/base.html:171 msgid "Unfollow" msgstr "" -#: kallithea/templates/base/base.html:184 +#: kallithea/templates/base/base.html:174 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "" -#: kallithea/templates/base/base.html:185 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:190 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "Keine Übereinstimmungen gefunden" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:234 -#: kallithea/templates/base/base.html:235 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "Öffentliches Logbuch" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:245 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:248 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:253 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:276 -#: kallithea/templates/base/base.html:277 -#: kallithea/templates/pullrequests/pullrequest_show_my.html:4 -#: kallithea/templates/pullrequests/pullrequest_show_my.html:8 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:6 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "Meine Pull Requests" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "Nicht eingeloggt" -#: kallithea/templates/base/base.html:303 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "" -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "Passwort vergessen?" -#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "" -#: kallithea/templates/base/base.html:402 -msgid "No matches found" -msgstr "Keine Übereinstimmungen gefunden" - -#: kallithea/templates/base/base.html:531 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:540 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" #: kallithea/templates/base/default_perms_box.html:14 #, fuzzy -#| msgid "Repository Defaults" msgid "Inherit defaults" msgstr "Repositorystandards" @@ -4753,52 +4718,50 @@ msgstr "" #: kallithea/templates/base/root.html:31 -#: kallithea/templates/pullrequests/pullrequest_show_all.html:30 -msgid "Open New Pull Request" -msgstr "Einen neuen Pull Request eröffnen" +#, fuzzy +msgid "Open New Pull Request from {0}" +msgstr "Kommentar von Pull Request" #: kallithea/templates/base/root.html:32 -msgid "Open New Pull Request for Selected Changesets" +msgid "Open New Pull Request for {0} → {1}" msgstr "" #: kallithea/templates/base/root.html:33 -msgid "Show Selected Changesets __S → __E" -msgstr "" +#, fuzzy +msgid "Show Selected Changesets {0} → {1}" +msgstr "Ausgewähltes Changeset anzeigen __S" #: kallithea/templates/base/root.html:34 -msgid "Show Selected Changeset __S" -msgstr "Ausgewähltes Changeset anzeigen __S" +msgid "Selection Link" +msgstr "" #: kallithea/templates/base/root.html:35 -msgid "Selection Link" +#: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 +msgid "Collapse Diff" msgstr "" #: kallithea/templates/base/root.html:36 -#: kallithea/templates/changeset/diff_block.html:8 -msgid "Collapse Diff" +msgid "Expand Diff" msgstr "" #: kallithea/templates/base/root.html:37 -msgid "Expand Diff" +msgid "Failed to revoke permission" msgstr "" #: kallithea/templates/base/root.html:38 -msgid "Failed to revoke permission" -msgstr "" +msgid "Confirm to revoke permission for {0}: {1} ?" +msgstr "Widerruf der Rechte für {0}: {1} bestätigen?" #: kallithea/templates/base/root.html:39 -msgid "Confirm to revoke permission for {0}: {1} ?" -msgstr "Widerruf der Rechte für {0}: {1} bestätigen?" +msgid "enabled" +msgstr "Aktiviert" #: kallithea/templates/base/root.html:40 -msgid "enabled" -msgstr "Aktiviert" - -#: kallithea/templates/base/root.html:41 msgid "disabled" msgstr "Deaktiviert" -#: kallithea/templates/base/root.html:43 +#: kallithea/templates/base/root.html:42 msgid "Specify changeset" msgstr "Changeset angeben" @@ -4816,7 +4779,6 @@ #: kallithea/templates/branches/branches.html:53 #: kallithea/templates/branches/branches_data.html:10 #: kallithea/templates/changelog/changelog_summary_data.html:10 -#: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/tags/tags.html:53 #: kallithea/templates/tags/tags_data.html:10 msgid "Author" @@ -4856,7 +4818,7 @@ msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changelog/changelog.html:52 +#: kallithea/templates/changelog/changelog.html:49 msgid "Clear selection" msgstr "" @@ -4884,51 +4846,54 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Set changeset status" +msgid "Changeset status: %s by %s" +msgstr "Setze Changesetstatus" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "Branch %s" -#: kallithea/templates/changelog/changelog.html:291 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "Bisher gibt es keine Änderungen" @@ -4944,7 +4909,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4974,23 +4939,22 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy -#| msgid "private repository" msgid "Push new repository" msgstr "privates Repository" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "" @@ -5008,13 +4972,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -5024,7 +4988,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -5044,19 +5008,17 @@ #: kallithea/templates/changeset/changeset.html:135 #, fuzzy -#| msgid "Created by" msgid "Replaced by:" msgstr "Erstellt von" #: kallithea/templates/changeset/changeset.html:149 #, fuzzy -#| msgid "Created by" msgid "Preceded by:" msgstr "Erstellt von" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:314 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -5064,8 +5026,8 @@ msgstr[1] "" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:316 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -5074,21 +5036,19 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:335 -#: kallithea/templates/pullrequests/pullrequest_show.html:359 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy -#| msgid "revisions" msgid "No revisions" msgstr "revisionen" #: kallithea/templates/changeset/changeset_file_comment.html:21 #, fuzzy -#| msgid "Comment from pull request" msgid "on pull request" msgstr "Kommentar von Pull Request" @@ -5097,107 +5057,91 @@ msgstr "Kein Titel" #: kallithea/templates/changeset/changeset_file_comment.html:24 -#, fuzzy, python-format -#| msgid "%s changesets" +#, fuzzy msgid "on this changeset" msgstr "%s Changesets" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 msgid "Delete comment?" msgstr "Kommentar löschen?" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy -#| msgid "Latest Changes" msgid "Status change" msgstr "Letzte Änderungen" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +msgid "Commenting on line." msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "Kommentarvorschau" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "Setze Changesetstatus" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "Keine Änderungen" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Kommentar von Pull Request" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "Schließen" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "Kommentar" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "Vorschau" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "%d Kommentar" msgstr[1] "%d Kommentare" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "%d inline" msgstr[1] "%d inline" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "%d generell" msgstr[1] "%d generell" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -msgid "Set changeset status" -msgstr "Setze Changesetstatus" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "Keine Änderungen" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -msgid "Close" -msgstr "Schließen" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5207,29 +5151,28 @@ msgid "Files affected" msgstr "" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "Gelöscht" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "Umbenannt" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "Gelöscht" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "Umbenannt" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "" @@ -5238,6 +5181,11 @@ msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5250,29 +5198,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:306 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 #, python-format msgid "%s changesets" msgstr "%s Changesets" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "zurück" @@ -5283,28 +5231,28 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:301 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5360,20 +5308,33 @@ msgstr "Hallo %s" #: kallithea/templates/email_templates/password_reset.html:6 -msgid "We received a request to create a new password for your account." +#, fuzzy +msgid "We have received a request to reset the password for your account." msgstr "" "Wir haben eine Anforderung erhalten, für deinen Account ein neues " "Passwort zu erstellen." -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "You can generate it by clicking following URL" -msgstr "Du kannst es über die folgende URL erstellen" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." +msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 -msgid "Please ignore this email if you did not request a new password ." -msgstr "" -"Bitte ignoriere diese E-Mail, wenn du kein neues Passwort angefordert " -"hast." +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 +msgid "" +"Should you not be able to use the link above, please type the following " +"code into the password reset form" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:16 +msgid "" +"If it weren't you who requested the password reset, just disregard this " +"message." +msgstr "" #: kallithea/templates/email_templates/pull_request.html:5 #, python-format @@ -5452,8 +5413,9 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "" +#, fuzzy +msgid "New file type" +msgstr "neue Datei" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5583,10 +5545,19 @@ msgid "Binary file (%s)" msgstr "" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +#, fuzzy +msgid "File is too big to display." msgstr "Die Datei ist zu groß, um sie anzuzeigen" +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." +msgstr "" + #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" msgstr "" @@ -5733,7 +5704,6 @@ #: kallithea/templates/pullrequests/pullrequest_data.html:14 #, fuzzy -#| msgid "revoke" msgid "Vote" msgstr "entziehen" @@ -5774,20 +5744,16 @@ #: kallithea/templates/pullrequests/pullrequest_data.html:70 #, fuzzy, python-format -#| msgid "Confirm to delete this group: %s with %s repository" -#| msgid_plural "Confirm to delete this group: %s with %s repositories" msgid "Confirm again to delete this pull request with %s comments" msgstr "Löschen der Gruppe bestätigen: %s mit %s Repository" #: kallithea/templates/pullrequests/pullrequest_show.html:6 #, fuzzy, python-format -#| msgid "Pull request #%s" msgid "%s Pull Request %s" msgstr "Pull Request #%s" #: kallithea/templates/pullrequests/pullrequest_show.html:10 #, fuzzy, python-format -#| msgid "Pull request #%s from %s#%s" msgid "Pull request %s from %s#%s" msgstr "Pull Request #%s von %s#%s" @@ -5835,98 +5801,111 @@ msgid "Target" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:131 +#: kallithea/templates/pullrequests/pullrequest_show.html:124 +msgid "" +"This is just a range of changesets and doesn't have a target or a real " +"merge ancestor." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:133 msgid "Pull changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:154 -msgid "Created by" -msgstr "Erstellt von" - -#: kallithea/templates/pullrequests/pullrequest_show.html:169 +#: kallithea/templates/pullrequests/pullrequest_show.html:173 msgid "Update" msgstr "Aktualisierung" -#: kallithea/templates/pullrequests/pullrequest_show.html:187 +#: kallithea/templates/pullrequests/pullrequest_show.html:191 msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:209 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "Pull Request Reviewers" -#: kallithea/templates/pullrequests/pullrequest_show.html:234 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 msgid "Remove reviewer" msgstr "Reviewer entfernen" -#: kallithea/templates/pullrequests/pullrequest_show.html:246 +#: kallithea/templates/pullrequests/pullrequest_show.html:261 msgid "Type name of reviewer to add" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:254 +#: kallithea/templates/pullrequests/pullrequest_show.html:269 msgid "Potential Reviewers" msgstr "Potentielle Reviewer" -#: kallithea/templates/pullrequests/pullrequest_show.html:257 +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:280 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:281 -msgid "Save as New Pull Request" +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#, fuzzy +msgid "Save Updates as New Pull Request" msgstr "Als neuen Pull Request speichern" -#: kallithea/templates/pullrequests/pullrequest_show.html:282 +#: kallithea/templates/pullrequests/pullrequest_show.html:297 msgid "Cancel Changes" msgstr "Änderungen verwerfen" -#: kallithea/templates/pullrequests/pullrequest_show.html:292 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "Inhalt des Pull Requests" -#: kallithea/templates/pullrequests/pullrequest_show_all.html:4 +#: kallithea/templates/pullrequests/pullrequest_show_all.html:6 #, python-format msgid "%s Pull Requests" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show_all.html:9 -#, python-format -msgid "Pull Requests from %s'" +#: kallithea/templates/pullrequests/pullrequest_show_all.html:11 +#, fuzzy, python-format +msgid "Pull Requests from '%s'" msgstr "Pull Requests von '%s'" -#: kallithea/templates/pullrequests/pullrequest_show_all.html:11 +#: kallithea/templates/pullrequests/pullrequest_show_all.html:13 #, python-format msgid "Pull Requests to '%s'" msgstr "Pull Requests für '%s'" -#: kallithea/templates/pullrequests/pullrequest_show_all.html:35 +#: kallithea/templates/pullrequests/pullrequest_show_all.html:32 +msgid "Open New Pull Request" +msgstr "Einen neuen Pull Request eröffnen" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:37 #, python-format msgid "Show Pull Requests to %s" msgstr "Zeige Pull Requests für '%s'" -#: kallithea/templates/pullrequests/pullrequest_show_all.html:37 +#: kallithea/templates/pullrequests/pullrequest_show_all.html:39 #, python-format msgid "Show Pull Requests from '%s'" msgstr "Zeige Pull Requests von '%s'" -#: kallithea/templates/pullrequests/pullrequest_show_all.html:47 -#: kallithea/templates/pullrequests/pullrequest_show_my.html:26 -msgid "Hide closed pull requests (only show open pull requests)" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show_all.html:49 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28 +msgid "Hide closed pull requests (only show open pull requests)" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:51 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:30 msgid "Show closed pull requests (in addition to open pull requests)" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show_my_data.html:3 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:35 msgid "Pull Requests Created by Me" msgstr "Von mir erstellte Pull Requests" -#: kallithea/templates/pullrequests/pullrequest_show_my_data.html:6 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:38 msgid "Pull Requests I Participate In" msgstr "" @@ -6177,14 +6156,9 @@ #~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL" #~ msgstr "" -#~ "Ungültige Clone-URL, gültige Clone-URL" -#~ " (http(s)/svn+http(s)/ssh) angeben" #~ msgid "Revisions %(revs)s are already part of pull request or have set status" #~ msgstr "" -#~ "Die Revisionen %(revs)s sind bereits " -#~ "Bestandteil des Pull Requests oder haben" -#~ " den Status" #~ msgid "Defaults" #~ msgstr "Voreinstellungen" @@ -6208,9 +6182,8 @@ #~ msgstr "löschen" #~ msgid "" -#~ "Your user is in an external source" -#~ " of record; some details cannot be" -#~ " managed here" +#~ "_: \n" +#~ "" #~ msgstr "" #~ msgid "Permissions Administration" @@ -6264,20 +6237,6 @@ #~ msgid "Non-changeable id" #~ msgstr "" -#~ msgid "" -#~ "In case this repository is renamed " -#~ "or moved into another group the " -#~ "repository URL changes.\n" -#~ " Using the above " -#~ "URL guarantees that this repository will" -#~ " always be accessible under such URL." -#~ "\n" -#~ " Useful for CI " -#~ "systems, or any other cases that " -#~ "you need to hardcode the URL into" -#~ " 3rd party service." -#~ msgstr "" - #~ msgid "edit" #~ msgstr "bearbeiten" @@ -6323,12 +6282,6 @@ #~ msgid "Destroy old data" #~ msgstr "" -#~ msgid "" -#~ "Check this option to remove references" -#~ " to repositories that no longer exist" -#~ " in on the filesystem." -#~ msgstr "" - #~ msgid "Meta-Tagging" #~ msgstr "" @@ -6338,29 +6291,12 @@ #~ msgid "user groups" #~ msgstr "Benutzergruppen" -#~ msgid "" -#~ "This user is in an external source" -#~ " of record (%s); some details cannot" -#~ " be managed here." -#~ msgstr "" - #~ msgid "Inherit from defaults" #~ msgstr "" -#~ msgid "" -#~ "Select to inherit permissions from %s" -#~ " permissions settings, and default IP " -#~ "address whitelist." -#~ msgstr "" - #~ msgid "show" #~ msgstr "" -#~ msgid "" -#~ "Changeset status: %s\n" -#~ "Click to open associated pull request #%s" -#~ msgstr "" - #~ msgid "Push new repo" #~ msgstr "" @@ -6385,11 +6321,6 @@ #~ msgid "Comment on changeset" #~ msgstr "" -#~ msgid "" -#~ "Use @username inside this text to " -#~ "send notification to another local user." -#~ msgstr "" - #~ msgid "revision" #~ msgstr "" @@ -6416,3 +6347,56 @@ #~ msgid "with subrepos" #~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "Dieser Pull Request kann mit Änderungen in %s aktualisiert werden:" + +#~ msgid "Your new password" +#~ msgstr "Dein neues Passwort" + +#~ msgid "Your new Kallithea password:%s" +#~ msgstr "Ihr neues Kallithea-Passwort: %s" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "" + +#~ msgid "Open New Pull Request for Selected Changesets" +#~ msgstr "" + +#~ msgid "Show Selected Changesets __S → __E" +#~ msgstr "" + +#~ msgid "Commenting on line {1}." +#~ msgstr "" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "Kommentarvorschau" + +#~ msgid "Preview" +#~ msgstr "Vorschau" + +#~ msgid "Use @username inside this text to notify another user." +#~ msgstr "" + +#~ msgid "You can generate it by clicking following URL" +#~ msgstr "Du kannst es über die folgende URL erstellen" + +#~ msgid "Please ignore this email if you did not request a new password ." +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "Created by" +#~ msgstr "Erstellt von" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/el/LC_MESSAGES/kallithea.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/i18n/el/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,6015 @@ +# Greek translations for Kallithea. +# Copyright (C) 2015 Various authors, licensing as GPLv3 +# This file is distributed under the same license as the Kallithea project. +# Automatically generated, 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: Kallithea 0.3\n" +"Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" +"PO-Revision-Date: 2016-02-10 12:01+0000\n" +"Last-Translator: Asterios Dimitriou \n" +"Language-Team: Greek " +"\n" +"Language: el\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 2.5-dev\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 +msgid "There are no changesets yet" +msgstr "Δεν υπάρχουν σετ αλλαγών ακόμα" + +#: kallithea/controllers/changelog.py:164 +#: kallithea/controllers/admin/permissions.py:61 +#: kallithea/controllers/admin/permissions.py:65 +#: kallithea/controllers/admin/permissions.py:69 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:8 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7 +#: kallithea/templates/base/perms_summary.html:14 +msgid "None" +msgstr "Χωρίς" + +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 +msgid "(closed)" +msgstr "(κλειστό)" + +#: kallithea/controllers/changeset.py:88 +msgid "Show whitespace" +msgstr "Εμφάνιση κενού" + +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 +#: kallithea/templates/files/diff_2way.html:55 +msgid "Ignore whitespace" +msgstr "Αγνόηση κενού" + +#: kallithea/controllers/changeset.py:168 +#, python-format +msgid "Increase diff context to %(num)s lines" +msgstr "Αύξηση του diff πλαισίου σε %(num)s γραμμές" + +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 +msgid "Such revision does not exist for this repository" +msgstr "Δεν υπάρχει τέτοια αναθεώρηση για αυτό το αποθετήριο" + +#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 +msgid "Select changeset" +msgstr "Επιλογή σετ αλλαγών" + +#: kallithea/controllers/compare.py:261 +msgid "Cannot compare repositories without using common ancestor" +msgstr "" +"Δεν μπορεί να γίνει σύγκριση αποθετηρίων χωρίς να χρησιμοποιηθεί κοινός " +"πρόγονος" + +#: kallithea/controllers/error.py:71 +msgid "No response" +msgstr "Χωρίς απόκριση" + +#: kallithea/controllers/error.py:72 +msgid "Unknown error" +msgstr "Άγνωστο λάθος" + +#: kallithea/controllers/error.py:100 +msgid "The request could not be understood by the server due to malformed syntax." +msgstr "" +"Η αίτηση δεν μπόρεσε να ερμηνευτεί από τον εξυπηρετητή λόγω κακής " +"διατύπωσης." + +#: kallithea/controllers/error.py:103 +msgid "Unauthorized access to resource" +msgstr "Ανεξουσιοδοτημένη πρόσβαση στον πόρο" + +#: kallithea/controllers/error.py:105 +msgid "You don't have permission to view this page" +msgstr "Δεν έχετε άδεια για να εμφανίσετε αυτή τη σελίδα" + +#: kallithea/controllers/error.py:107 +msgid "The resource could not be found" +msgstr "Ο πόρος δεν μπορεί να βρεθεί" + +#: kallithea/controllers/error.py:109 +msgid "" +"The server encountered an unexpected condition which prevented it from " +"fulfilling the request." +msgstr "" +"Ο εξυπηρετητής συνάντησε μια απρόσμενη κατάσταση που τον απέτρεψαν να " +"πραγματοποιήσει την αίτηση." + +#: kallithea/controllers/feed.py:55 +#, python-format +msgid "Changes on %s repository" +msgstr "Αλλαγές στο αποθετήριο %s" + +#: kallithea/controllers/feed.py:56 +#, python-format +msgid "%s %s feed" +msgstr "%s %s τροφοδοσία" + +#: kallithea/controllers/feed.py:87 +#: kallithea/templates/changeset/changeset.html:182 +#: kallithea/templates/changeset/changeset.html:195 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 +msgid "Changeset was too big and was cut off..." +msgstr "Το σετ αλλαγών ήταν πολύ μεγάλο και περικόπηκε..." + +#: kallithea/controllers/feed.py:91 +#, python-format +msgid "%s committed on %s" +msgstr "%s συνέβαλε στο %s" + +#: kallithea/controllers/files.py:92 +msgid "Click here to add new file" +msgstr "Κλικ εδώ για προθήκη νέου αρχείου" + +#: kallithea/controllers/files.py:93 +#, python-format +msgid "There are no files yet. %s" +msgstr "Δεν υπάρχουν αρχεία ακόμα. %s" + +#: kallithea/controllers/files.py:195 +#, python-format +msgid "%s at %s" +msgstr "%s την %s" + +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 +#, python-format +msgid "This repository has been locked by %s on %s" +msgstr "Το αποθετήριο κλειδώθηκε από %s την %s" + +#: kallithea/controllers/files.py:319 +#, fuzzy +msgid "You can only delete files with revision being a valid branch" +msgstr "" +"Μπορείτε να διαγράψετε μόνο αρχεία σε αναθεώρηση που βρίσκονται σε έγκυρη" +" διακλάδωση " + +#: kallithea/controllers/files.py:330 +#, python-format +msgid "Deleted file %s via Kallithea" +msgstr "Διαγραφή αρχείου %s μέσω του Kallithea" + +#: kallithea/controllers/files.py:352 +#, python-format +msgid "Successfully deleted file %s" +msgstr "Επιτυχής διαγραφή αρχείου %s" + +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 +msgid "Error occurred during commit" +msgstr "Συνέβη λάθος κατά το commit" + +#: kallithea/controllers/files.py:379 +#, fuzzy +msgid "You can only edit files with revision being a valid branch" +msgstr "" +"Μπορείτε να επεξεργαστείτε μόνο αρχεία σε αναθεώρηση που βρίσκονται σε " +"έγκυρη διακλάδωση " + +#: kallithea/controllers/files.py:393 +#, python-format +msgid "Edited file %s via Kallithea" +msgstr "Επεξεργασία αρχείου %s μέσω του Kallithea" + +#: kallithea/controllers/files.py:409 +msgid "No changes" +msgstr "Καμία αλλαγή" + +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 +#, python-format +msgid "Successfully committed to %s" +msgstr "Επιτυχής παράδοση σε %s" + +#: kallithea/controllers/files.py:445 +msgid "Added file via Kallithea" +msgstr "Προσθήκη αρχείου μέσω Kallithea" + +#: kallithea/controllers/files.py:466 +msgid "No content" +msgstr "Χωρίς περιεχόμενο" + +#: kallithea/controllers/files.py:470 +msgid "No filename" +msgstr "Χωρίς όνομα αρχείου" + +#: kallithea/controllers/files.py:495 +msgid "Location must be relative path and must not contain .. in path" +msgstr "" +"Η τοποθεσία πρέπει να είναι σχετική διαδρομή και να μην περιέχει .. μέσα " +"της" + +#: kallithea/controllers/files.py:528 +msgid "Downloads disabled" +msgstr "Οι μεταφορτώσεις απενεργοποιήθηκαν" + +#: kallithea/controllers/files.py:539 +#, python-format +msgid "Unknown revision %s" +msgstr "Άγνωστη αναθεώρηση %s" + +#: kallithea/controllers/files.py:541 +msgid "Empty repository" +msgstr "Άδειο αποθετήριο" + +#: kallithea/controllers/files.py:543 +msgid "Unknown archive type" +msgstr "Άγνωστος τύπος αρχειοθέτησης" + +#: kallithea/controllers/files.py:773 +#: kallithea/templates/changeset/changeset_range.html:9 +#: kallithea/templates/email_templates/pull_request.html:15 +#: kallithea/templates/pullrequests/pullrequest.html:97 +msgid "Changesets" +msgstr "Σετ αλλαγών" + +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 +#: kallithea/templates/branches/branches.html:10 +msgid "Branches" +msgstr "Κλάδοι" + +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 +#: kallithea/templates/tags/tags.html:10 +msgid "Tags" +msgstr "Ετικέτες" + +#: kallithea/controllers/forks.py:186 +#, python-format +msgid "An error occurred during repository forking %s" +msgstr "Συνέβει ένα λάθος κατά την διακλάδωση του αποθετηρίου %s" + +#: kallithea/controllers/home.py:84 +msgid "Groups" +msgstr "Ομάδες" + +#: kallithea/controllers/home.py:94 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 +#: kallithea/templates/admin/repos/repo_add.html:12 +#: kallithea/templates/admin/repos/repo_add.html:16 +#: kallithea/templates/admin/repos/repos.html:9 +#: kallithea/templates/admin/users/user_edit_advanced.html:6 +#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 +#: kallithea/templates/base/base.html:124 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 +msgid "Repositories" +msgstr "Αποθετήρια" + +#: kallithea/controllers/home.py:139 +#: kallithea/templates/files/files_add.html:32 +#: kallithea/templates/files/files_delete.html:23 +#: kallithea/templates/files/files_edit.html:32 +msgid "Branch" +msgstr "Κλάδος" + +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "" + +#: kallithea/controllers/home.py:151 +msgid "Tag" +msgstr "Ετικέτα" + +#: kallithea/controllers/home.py:157 +msgid "Bookmark" +msgstr "Σελιδοδείκτης" + +#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153 +#: kallithea/templates/journal/public_journal.html:4 +#: kallithea/templates/journal/public_journal.html:21 +msgid "Public Journal" +msgstr "Δημόσιο Ημερολόγιο" + +#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 +#: kallithea/templates/base/base.html:306 +#: kallithea/templates/journal/journal.html:4 +#: kallithea/templates/journal/journal.html:12 +msgid "Journal" +msgstr "Ημερολόγιο" + +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 +msgid "Bad captcha" +msgstr "Λάθος captcha" + +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "Εγγραφήκατε επιτυχώς στο %s" + +#: kallithea/controllers/login.py:195 +msgid "A password reset confirmation code has been sent" +msgstr "Στάλθηκε ένας κωδικός επιβεβαίωσης επαναφοράς του συνθηματικού" + +#: kallithea/controllers/login.py:244 +msgid "Invalid password reset token" +msgstr "Άκυρο τεκμήριο (token) επαναφοράς του συνθηματικού" + +#: kallithea/controllers/login.py:249 +#: kallithea/controllers/admin/my_account.py:167 +msgid "Successfully updated password" +msgstr "Το συνθηματικό ενημερώθηκε επιτυχώς" + +#: kallithea/controllers/pullrequests.py:123 +#, python-format +msgid "%s (closed)" +msgstr "%s (κλειστό)" + +#: kallithea/controllers/pullrequests.py:151 +#: kallithea/templates/changeset/changeset.html:12 +#: kallithea/templates/email_templates/changeset_comment.html:17 +msgid "Changeset" +msgstr "Σετ αλλαγών" + +#: kallithea/controllers/pullrequests.py:172 +msgid "Special" +msgstr "Ειδικός" + +#: kallithea/controllers/pullrequests.py:173 +msgid "Peer branches" +msgstr "Ομότιμοι κλάδοι" + +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 +#: kallithea/templates/switch_to_list.html:38 +#: kallithea/templates/bookmarks/bookmarks.html:10 +msgid "Bookmarks" +msgstr "Σελιδοδείκτες" + +#: kallithea/controllers/pullrequests.py:312 +#, python-format +msgid "Error creating pull request: %s" +msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s" + +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 +msgid "No description" +msgstr "Χωρίς περιγραφή" + +#: kallithea/controllers/pullrequests.py:365 +msgid "Successfully opened new pull request" +msgstr "Ένα νέο αίτημα έλξης (pull request) δημιουργήθηκε επιτυχώς" + +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 +#, python-format +msgid "Invalid reviewer \"%s\" specified" +msgstr "Καθορίστηκε άκυρος σχολιαστής \"%s\"" + +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 +msgid "Error occurred while creating pull request" +msgstr "Λάθος κατά τη δημιουργία αιτήματος έλξης (pull request)" + +#: kallithea/controllers/pullrequests.py:403 +msgid "Missing changesets since the previous pull request:" +msgstr "Ελλιπή σετ αλλαγών από την προηγούμενη αίτηση έλξης:" + +#: kallithea/controllers/pullrequests.py:410 +#, python-format +msgid "New changesets on %s %s since the previous pull request:" +msgstr "Καινούρια σετ αλλαγών στα %s %s από την προηγούμενη αίτηση έλξης:" + +#: kallithea/controllers/pullrequests.py:417 +msgid "Ancestor didn't change - show diff since previous version:" +msgstr "Το γονικό δεν άλλαξε - εμφάνισε τις διαφορές από την προηγούμενη έκδοση:" + +#: kallithea/controllers/pullrequests.py:424 +#, python-format +msgid "" +"This pull request is based on another %s revision and there is no simple " +"diff." +msgstr "" +"Αυτή η αίτηση έλξης είναι βασισμένη σε μία άλλη %s αναθεώρηση και δεν " +"υπάρχει ένα απλό diff." + +#: kallithea/controllers/pullrequests.py:426 +#, python-format +msgid "No changes found on %s %s since previous version." +msgstr "Δεν βρέθηκαν αλλαγές στο %s %s από την προηγούμενη έκδοση." + +#: kallithea/controllers/pullrequests.py:464 +#, python-format +msgid "Closed, replaced by %s ." +msgstr "Κλειστό, αντικαταστάθηκε από %s." + +#: kallithea/controllers/pullrequests.py:472 +msgid "Pull request update created" +msgstr "Δημιουργήθηκε ενημέρωση αιτήματος έλξης" + +#: kallithea/controllers/pullrequests.py:516 +msgid "Pull request updated" +msgstr "Ενημερώθηκε η αίτηση έλξης" + +#: kallithea/controllers/pullrequests.py:531 +msgid "Successfully deleted pull request" +msgstr "Επιτυχής διαγραφή αιτήματος έλξης" + +#: kallithea/controllers/pullrequests.py:597 +#, python-format +msgid "This pull request has already been merged to %s." +msgstr "Το αίτημα έλξης έχει ήδη συγχωνευτεί με το %s." + +#: kallithea/controllers/pullrequests.py:599 +msgid "This pull request has been closed and can not be updated." +msgstr "Αυτό το αίτημα έλξης έχει κλείσει και δεν μπορεί να ενημερωθεί." + +#: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 +msgid "No changesets found for updating this pull request." +msgstr "Δεν βρέθηκαν σετ αλλαγών για ενημέρωση αυτού του αιτήματος έλξης." + +#: kallithea/controllers/pullrequests.py:629 +#, python-format +msgid "Note: Branch %s has another head: %s." +msgstr "Σημείωση: Ο κλάδος %s έχει άλλη κεφαλή (head): %s." + +#: kallithea/controllers/pullrequests.py:635 +msgid "Git pull requests don't support updates yet." +msgstr "Αιτήματα έλξης του git δεν υποστηρίζουν ακόμα ενημερώσεις." + +#: kallithea/controllers/pullrequests.py:727 +msgid "No permission to change pull request status" +msgstr "Χωρίς δικαιώματα αλλαγής της κατάστασης του αιτήματος έλξης" + +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "Επιτυχής διαγραφή αιτήματος έλξης" + +#: kallithea/controllers/pullrequests.py:748 +msgid "Closing." +msgstr "Κλείνει." + +#: kallithea/controllers/search.py:135 +msgid "Invalid search query. Try quoting it." +msgstr "Άκυρο αίτημα αναζήτησης. Δοκιμάστε με εισαγωγικά." + +#: kallithea/controllers/search.py:140 +msgid "There is no index to search in. Please run whoosh indexer" +msgstr "" +"Δεν υπάρχει ευρετήριο για την αναζήτηση. Παρακαλώ τρέξτε τον whoosh για " +"την δημιουργία του" + +#: kallithea/controllers/search.py:144 +msgid "An error occurred during search operation." +msgstr "Ένα λάθος συνέβη κατά την διαδικασία αναζήτησης." + +#: kallithea/controllers/summary.py:181 +#: kallithea/templates/summary/summary.html:384 +msgid "No data ready yet" +msgstr "Δεν υπάρχουν ακόμα έτοιμα δεδομένα" + +#: kallithea/controllers/summary.py:184 +#: kallithea/templates/summary/summary.html:98 +msgid "Statistics are disabled for this repository" +msgstr "Τα στατιστικά είναι απενεργοποιημένα για αυτό το αποθετήριο" + +#: kallithea/controllers/admin/auth_settings.py:135 +msgid "Auth settings updated successfully" +msgstr "Οι ρυθμίσεις εξουσιοδότησης ενημερώθηκαν επιτυχώς" + +#: kallithea/controllers/admin/auth_settings.py:146 +msgid "error occurred during update of auth settings" +msgstr "παρουσιάστηκε βλάβη κατά την ενημέρωση των ρυθμίσεων εξουσιοδότησης" + +#: kallithea/controllers/admin/defaults.py:97 +msgid "Default settings updated successfully" +msgstr "Οι προεπιλεγμένες ρυθμίσεις ενημερώθηκαν επιτυχώς" + +#: kallithea/controllers/admin/defaults.py:112 +msgid "Error occurred during update of defaults" +msgstr "Συνέβη μία βλάβη κατά την ενημέρωση των προεπιλογών" + +#: kallithea/controllers/admin/gists.py:58 +#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "Πάντα" + +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/users.py:285 +msgid "5 minutes" +msgstr "5 λεπτά" + +#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/users.py:286 +msgid "1 hour" +msgstr "1 ώρα" + +#: kallithea/controllers/admin/gists.py:61 +#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/users.py:287 +msgid "1 day" +msgstr "1 ημέρα" + +#: kallithea/controllers/admin/gists.py:62 +#: kallithea/controllers/admin/my_account.py:247 +#: kallithea/controllers/admin/users.py:288 +msgid "1 month" +msgstr "1 μήνας" + +#: kallithea/controllers/admin/gists.py:66 +#: kallithea/controllers/admin/my_account.py:249 +#: kallithea/controllers/admin/users.py:290 +msgid "Lifetime" +msgstr "Διάρκεια ζωής" + +#: kallithea/controllers/admin/gists.py:145 +msgid "Error occurred during gist creation" +msgstr "Συνέβη μία βλάβη κατά τη δημιουργία του gist" + +#: kallithea/controllers/admin/gists.py:183 +#, python-format +msgid "Deleted gist %s" +msgstr "Διαγράφηκε το gist %s" + +#: kallithea/controllers/admin/gists.py:232 +msgid "Unmodified" +msgstr "Mη τροποποιημένo" + +#: kallithea/controllers/admin/gists.py:261 +msgid "Successfully updated gist content" +msgstr "Το περιεχόμενο του gist ενημερώθηκε επιτυχώς" + +#: kallithea/controllers/admin/gists.py:266 +msgid "Successfully updated gist data" +msgstr "Τα δεδομένα του gist ενημερώθηκαν επιτυχώς" + +#: kallithea/controllers/admin/gists.py:269 +#, python-format +msgid "Error occurred during update of gist %s" +msgstr "Σφάλμα συνέβη κατά την ενημέρωση του gist %s" + +#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215 +#: kallithea/model/user.py:237 +msgid "You can't edit this user since it's crucial for entire application" +msgstr "" +"Δεν μπορείτε να επεξεργαστείτε αυτόν το χρήστη καθώς είναι κρίσιμος για " +"όλη την εφαρμογή" + +#: kallithea/controllers/admin/my_account.py:129 +msgid "Your account was updated successfully" +msgstr "Ο λογαριασμός σας ενημερώθηκε επιτυχώς" + +#: kallithea/controllers/admin/my_account.py:144 +#: kallithea/controllers/admin/users.py:201 +#, python-format +msgid "Error occurred during update of user %s" +msgstr "Συνέβη ένα σφάλμα κατά την ενημέρωση του χρήστη %s" + +#: kallithea/controllers/admin/my_account.py:178 +msgid "Error occurred during update of user password" +msgstr "Συνέβη ένα σφάλμα κατά την ενημέρωση του κωδικού του χρήστη" + +#: kallithea/controllers/admin/my_account.py:220 +#: kallithea/controllers/admin/users.py:414 +#, python-format +msgid "Added email %s to user" +msgstr "Προστέθηκε το email %s στον χρήστη" + +#: kallithea/controllers/admin/my_account.py:226 +#: kallithea/controllers/admin/users.py:420 +msgid "An error occurred during email saving" +msgstr "Συνέβη ένα σφάλμα κατά την αποθήκευση του email" + +#: kallithea/controllers/admin/my_account.py:235 +#: kallithea/controllers/admin/users.py:432 +msgid "Removed email from user" +msgstr "Αφαιρέθηκε το email από τον χρήστη" + +#: kallithea/controllers/admin/my_account.py:259 +#: kallithea/controllers/admin/users.py:307 +msgid "API key successfully created" +msgstr "Το API κλειδί δημιουργήθηκε επιτυχώς" + +#: kallithea/controllers/admin/my_account.py:271 +#: kallithea/controllers/admin/users.py:320 +msgid "API key successfully reset" +msgstr "Το API κλειδί επαναφέρθηκε επιτυχώς" + +#: kallithea/controllers/admin/my_account.py:275 +#: kallithea/controllers/admin/users.py:324 +msgid "API key successfully deleted" +msgstr "Το API κλειδί διαγράφηκε επιτυχώς" + +#: kallithea/controllers/admin/permissions.py:62 +#: kallithea/controllers/admin/permissions.py:66 +#: kallithea/controllers/admin/permissions.py:70 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:9 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8 +#: kallithea/templates/base/perms_summary.html:15 +msgid "Read" +msgstr "Ανάγνωση" + +#: kallithea/controllers/admin/permissions.py:63 +#: kallithea/controllers/admin/permissions.py:67 +#: kallithea/controllers/admin/permissions.py:71 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:10 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9 +#: kallithea/templates/base/perms_summary.html:16 +msgid "Write" +msgstr "Εγγραφή" + +#: kallithea/controllers/admin/permissions.py:64 +#: kallithea/controllers/admin/permissions.py:68 +#: kallithea/controllers/admin/permissions.py:72 +#: kallithea/templates/admin/auth/auth_settings.html:9 +#: kallithea/templates/admin/defaults/defaults.html:9 +#: kallithea/templates/admin/permissions/permissions.html:9 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:9 +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:9 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47 +#: kallithea/templates/admin/repo_groups/repo_groups.html:10 +#: kallithea/templates/admin/repos/repo_add.html:10 +#: kallithea/templates/admin/repos/repo_add.html:14 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:11 +#: kallithea/templates/admin/repos/repos.html:9 +#: kallithea/templates/admin/settings/settings.html:9 +#: kallithea/templates/admin/user_groups/user_group_add.html:8 +#: kallithea/templates/admin/user_groups/user_group_edit.html:9 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47 +#: kallithea/templates/admin/user_groups/user_groups.html:10 +#: kallithea/templates/admin/users/user_add.html:8 +#: kallithea/templates/admin/users/user_edit.html:9 +#: kallithea/templates/admin/users/user_edit_profile.html:105 +#: kallithea/templates/admin/users/users.html:10 +#: kallithea/templates/admin/users/users.html:55 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 +#: kallithea/templates/base/perms_summary.html:17 +msgid "Admin" +msgstr "Διαχειριστής" + +#: kallithea/controllers/admin/permissions.py:75 +#: kallithea/controllers/admin/permissions.py:86 +#: kallithea/controllers/admin/permissions.py:91 +#: kallithea/controllers/admin/permissions.py:94 +#: kallithea/controllers/admin/permissions.py:97 +#: kallithea/controllers/admin/permissions.py:100 +#: kallithea/templates/admin/auth/auth_settings.html:40 +msgid "Disabled" +msgstr "Απενεργοποιημένο" + +#: kallithea/controllers/admin/permissions.py:77 +msgid "Allowed with manual account activation" +msgstr "Επιτρέπεται με χειροποίητη ενεργοποίηση του λογαριασμού" + +#: kallithea/controllers/admin/permissions.py:79 +msgid "Allowed with automatic account activation" +msgstr "Επιτρέπεται με αυτόματη ενεργοποίηση του λογαριασμού" + +#: kallithea/controllers/admin/permissions.py:82 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 +msgid "Manual activation of external account" +msgstr "Χειροποίητη ενεργοποίηση εξωτερικού λογαριασμού" + +#: kallithea/controllers/admin/permissions.py:83 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 +msgid "Automatic activation of external account" +msgstr "Αυτόματη ενεργοποίηση εξωτερικού λογαριασμού" + +#: kallithea/controllers/admin/permissions.py:87 +#: kallithea/controllers/admin/permissions.py:90 +#: kallithea/controllers/admin/permissions.py:95 +#: kallithea/controllers/admin/permissions.py:98 +#: kallithea/controllers/admin/permissions.py:101 +#: kallithea/templates/admin/auth/auth_settings.html:40 +msgid "Enabled" +msgstr "Ενεργό" + +#: kallithea/controllers/admin/permissions.py:124 +msgid "Global permissions updated successfully" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:139 +msgid "Error occurred during update of permissions" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:187 +#, python-format +msgid "Error occurred during creation of repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:192 +#, python-format +msgid "Created repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:249 +#, python-format +msgid "Updated repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:265 +#, python-format +msgid "Error occurred during update of repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:283 +#, python-format +msgid "This group contains %s repositories and cannot be deleted" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:290 +#, python-format +msgid "This group contains %s subgroups and cannot be deleted" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:296 +#, python-format +msgid "Removed repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:301 +#, python-format +msgid "Error occurred during deletion of repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 +#: kallithea/controllers/admin/user_groups.py:340 +msgid "Cannot revoke permission for yourself as admin" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:419 +msgid "Repository group permissions updated" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 +#: kallithea/controllers/admin/user_groups.py:352 +msgid "An error occurred during revoking of permission" +msgstr "" + +#: kallithea/controllers/admin/repos.py:151 +#, python-format +msgid "Error creating repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:212 +#, python-format +msgid "Created repository %s from %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:221 +#, python-format +msgid "Forked repository %s as %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:224 +#, python-format +msgid "Created repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:261 +#, python-format +msgid "Repository %s updated successfully" +msgstr "" + +#: kallithea/controllers/admin/repos.py:282 +#, python-format +msgid "Error occurred during update of repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:309 +#, python-format +msgid "Detached %s forks" +msgstr "" + +#: kallithea/controllers/admin/repos.py:312 +#, python-format +msgid "Deleted %s forks" +msgstr "" + +#: kallithea/controllers/admin/repos.py:317 +#, python-format +msgid "Deleted repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:320 +#, python-format +msgid "Cannot delete repository %s which still has forks" +msgstr "" + +#: kallithea/controllers/admin/repos.py:325 +#, python-format +msgid "An error occurred during deletion of %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:373 +msgid "Repository permissions updated" +msgstr "" + +#: kallithea/controllers/admin/repos.py:429 +msgid "An error occurred during creation of field" +msgstr "" + +#: kallithea/controllers/admin/repos.py:443 +msgid "An error occurred during removal of field" +msgstr "" + +#: kallithea/controllers/admin/repos.py:459 +msgid "-- Not a fork --" +msgstr "" + +#: kallithea/controllers/admin/repos.py:490 +msgid "Updated repository visibility in public journal" +msgstr "" + +#: kallithea/controllers/admin/repos.py:494 +msgid "An error occurred during setting this repository in public journal" +msgstr "" + +#: kallithea/controllers/admin/repos.py:511 +msgid "Nothing" +msgstr "" + +#: kallithea/controllers/admin/repos.py:513 +#, python-format +msgid "Marked repository %s as fork of %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:520 +msgid "An error occurred during this operation" +msgstr "" + +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 +msgid "Repository has been locked" +msgstr "" + +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 +msgid "Repository has been unlocked" +msgstr "" + +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 +msgid "An error occurred during unlocking" +msgstr "" + +#: kallithea/controllers/admin/repos.py:581 +msgid "Cache invalidation successful" +msgstr "" + +#: kallithea/controllers/admin/repos.py:585 +msgid "An error occurred during cache invalidation" +msgstr "" + +#: kallithea/controllers/admin/repos.py:600 +msgid "Pulled from remote location" +msgstr "" + +#: kallithea/controllers/admin/repos.py:603 +msgid "An error occurred during pull from remote location" +msgstr "" + +#: kallithea/controllers/admin/repos.py:636 +msgid "An error occurred during deletion of repository stats" +msgstr "" + +#: kallithea/controllers/admin/settings.py:141 +msgid "Updated VCS settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:145 +msgid "" +"Unable to activate hgsubversion support. The \"hgsubversion\" library is " +"missing" +msgstr "" + +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 +msgid "Error occurred while updating application settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:187 +#, python-format +msgid "Repositories successfully rescanned. Added: %s. Removed: %s." +msgstr "" + +#: kallithea/controllers/admin/settings.py:244 +msgid "Updated application settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:301 +msgid "Updated visualisation settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:306 +msgid "Error occurred during updating visualisation settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:332 +msgid "Please enter email address" +msgstr "" + +#: kallithea/controllers/admin/settings.py:347 +msgid "Send email task created" +msgstr "" + +#: kallithea/controllers/admin/settings.py:378 +msgid "Added new hook" +msgstr "" + +#: kallithea/controllers/admin/settings.py:392 +msgid "Updated hooks" +msgstr "" + +#: kallithea/controllers/admin/settings.py:396 +msgid "Error occurred during hook creation" +msgstr "" + +#: kallithea/controllers/admin/settings.py:422 +msgid "Whoosh reindex task scheduled" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:150 +#, python-format +msgid "Created user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:163 +#, python-format +msgid "Error occurred during creation of user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:201 +#, python-format +msgid "Updated user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:224 +#, python-format +msgid "Error occurred during update of user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:242 +msgid "Successfully deleted user group" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:247 +msgid "An error occurred during deletion of user group" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:314 +msgid "Target group cannot be the same" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:320 +msgid "User group permissions updated" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:440 +#: kallithea/controllers/admin/users.py:383 +msgid "Updated permissions" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:444 +#: kallithea/controllers/admin/users.py:387 +msgid "An error occurred during permissions saving" +msgstr "" + +#: kallithea/controllers/admin/users.py:133 +#, python-format +msgid "Created user %s" +msgstr "" + +#: kallithea/controllers/admin/users.py:148 +#, python-format +msgid "Error occurred during creation of user %s" +msgstr "" + +#: kallithea/controllers/admin/users.py:181 +msgid "User updated successfully" +msgstr "" + +#: kallithea/controllers/admin/users.py:217 +msgid "Successfully deleted user" +msgstr "" + +#: kallithea/controllers/admin/users.py:222 +msgid "An error occurred during deletion of user" +msgstr "" + +#: kallithea/controllers/admin/users.py:235 +msgid "The default user cannot be edited" +msgstr "" + +#: kallithea/controllers/admin/users.py:462 +#, python-format +msgid "Added IP address %s to user whitelist" +msgstr "" + +#: kallithea/controllers/admin/users.py:468 +msgid "An error occurred while adding IP address" +msgstr "" + +#: kallithea/controllers/admin/users.py:482 +msgid "Removed IP address from user whitelist" +msgstr "" + +#: kallithea/lib/auth.py:737 +#, python-format +msgid "IP %s not allowed" +msgstr "" + +#: kallithea/lib/auth.py:750 +msgid "Invalid API key" +msgstr "" + +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 +msgid "You need to be a registered user to perform this action" +msgstr "" + +#: kallithea/lib/auth.py:843 +msgid "You need to be signed in to view this page" +msgstr "" + +#: kallithea/lib/base.py:493 +msgid "Repository not found in the filesystem" +msgstr "" + +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 +msgid "Changeset not found" +msgstr "" + +#: kallithea/lib/diffs.py:66 +msgid "Binary file" +msgstr "" + +#: kallithea/lib/diffs.py:82 +msgid "Changeset was too big and was cut off, use diff menu to display this diff" +msgstr "" + +#: kallithea/lib/diffs.py:92 +msgid "No changes detected" +msgstr "" + +#: kallithea/lib/helpers.py:610 +#, python-format +msgid "Deleted branch: %s" +msgstr "" + +#: kallithea/lib/helpers.py:612 +#, python-format +msgid "Created tag: %s" +msgstr "" + +#: kallithea/lib/helpers.py:672 +#, python-format +msgid "Show all combined changesets %s->%s" +msgstr "" + +#: kallithea/lib/helpers.py:678 +msgid "Compare view" +msgstr "" + +#: kallithea/lib/helpers.py:697 +msgid "and" +msgstr "" + +#: kallithea/lib/helpers.py:698 +#, python-format +msgid "%s more" +msgstr "" + +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 +msgid "revisions" +msgstr "" + +#: kallithea/lib/helpers.py:723 +#, python-format +msgid "Fork name %s" +msgstr "" + +#: kallithea/lib/helpers.py:743 +#, python-format +msgid "Pull request %s" +msgstr "" + +#: kallithea/lib/helpers.py:753 +msgid "[deleted] repository" +msgstr "" + +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 +msgid "[created] repository" +msgstr "" + +#: kallithea/lib/helpers.py:757 +msgid "[created] repository as fork" +msgstr "" + +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 +msgid "[forked] repository" +msgstr "" + +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 +msgid "[updated] repository" +msgstr "" + +#: kallithea/lib/helpers.py:763 +msgid "[downloaded] archive from repository" +msgstr "" + +#: kallithea/lib/helpers.py:765 +msgid "[delete] repository" +msgstr "" + +#: kallithea/lib/helpers.py:773 +msgid "[created] user" +msgstr "" + +#: kallithea/lib/helpers.py:775 +msgid "[updated] user" +msgstr "" + +#: kallithea/lib/helpers.py:777 +msgid "[created] user group" +msgstr "" + +#: kallithea/lib/helpers.py:779 +msgid "[updated] user group" +msgstr "" + +#: kallithea/lib/helpers.py:781 +msgid "[commented] on revision in repository" +msgstr "" + +#: kallithea/lib/helpers.py:783 +msgid "[commented] on pull request for" +msgstr "" + +#: kallithea/lib/helpers.py:785 +msgid "[closed] pull request for" +msgstr "" + +#: kallithea/lib/helpers.py:787 +msgid "[pushed] into" +msgstr "" + +#: kallithea/lib/helpers.py:789 +msgid "[committed via Kallithea] into repository" +msgstr "" + +#: kallithea/lib/helpers.py:791 +msgid "[pulled from remote] into repository" +msgstr "" + +#: kallithea/lib/helpers.py:793 +msgid "[pulled] from" +msgstr "" + +#: kallithea/lib/helpers.py:795 +msgid "[started following] repository" +msgstr "" + +#: kallithea/lib/helpers.py:797 +msgid "[stopped following] repository" +msgstr "" + +#: kallithea/lib/helpers.py:1124 +#, python-format +msgid " and %s more" +msgstr "" + +#: kallithea/lib/helpers.py:1128 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 +msgid "No files" +msgstr "" + +#: kallithea/lib/helpers.py:1194 +msgid "new file" +msgstr "" + +#: kallithea/lib/helpers.py:1197 +msgid "mod" +msgstr "" + +#: kallithea/lib/helpers.py:1200 +msgid "del" +msgstr "" + +#: kallithea/lib/helpers.py:1203 +msgid "rename" +msgstr "" + +#: kallithea/lib/helpers.py:1208 +msgid "chmod" +msgstr "" + +#: kallithea/lib/helpers.py:1469 +#, python-format +msgid "" +"%s repository is not mapped to db perhaps it was created or renamed from " +"the filesystem please run the application again in order to rescan " +"repositories" +msgstr "" + +#: kallithea/lib/utils2.py:434 +#, python-format +msgid "%d year" +msgid_plural "%d years" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:435 +#, python-format +msgid "%d month" +msgid_plural "%d months" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:436 +#, python-format +msgid "%d day" +msgid_plural "%d days" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:437 +#, python-format +msgid "%d hour" +msgid_plural "%d hours" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:438 +#, python-format +msgid "%d minute" +msgid_plural "%d minutes" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:439 +#, python-format +msgid "%d second" +msgid_plural "%d seconds" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:455 +#, python-format +msgid "in %s" +msgstr "" + +#: kallithea/lib/utils2.py:457 +#, python-format +msgid "%s ago" +msgstr "" + +#: kallithea/lib/utils2.py:459 +#, python-format +msgid "in %s and %s" +msgstr "" + +#: kallithea/lib/utils2.py:462 +#, python-format +msgid "%s and %s ago" +msgstr "" + +#: kallithea/lib/utils2.py:465 +msgid "just now" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649 +msgid "Repository no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650 +msgid "Repository read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651 +msgid "Repository write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652 +msgid "Repository admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308 +msgid "Repository Group no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309 +msgid "Repository Group read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310 +msgid "Repository Group write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311 +msgid "Repository Group admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 +msgid "Kallithea Administrator" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670 +msgid "Repository creation disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671 +msgid "Repository creation enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675 +msgid "Repository forking disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676 +msgid "Repository forking enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403 +msgid "Register disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404 +msgid "Register new user with Kallithea with manual activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407 +msgid "Register new user with Kallithea with auto activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200 +msgid "Not Reviewed" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 +msgid "Approved" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 +msgid "Rejected" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203 +msgid "Under Review" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 +msgid "top level" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654 +msgid "Repository group no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655 +msgid "Repository group read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656 +msgid "Repository group write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657 +msgid "Repository group admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659 +msgid "User group no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660 +msgid "User group read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661 +msgid "User group write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662 +msgid "User group admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664 +msgid "Repository Group creation disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665 +msgid "Repository Group creation enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667 +msgid "User Group creation disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668 +msgid "User Group creation enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 +msgid "Registration disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679 +msgid "User Registration with manual account activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680 +msgid "User Registration with automatic account activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 +msgid "Repository creation enabled with write permission to a repository group" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 +msgid "Repository creation disabled with write permission to a repository group" +msgstr "" + +#: kallithea/model/comment.py:72 +#, python-format +msgid "on line %s" +msgstr "" + +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 +msgid "[Mention]" +msgstr "" + +#: kallithea/model/db.py:1671 +msgid "Default user has no access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1672 +msgid "Default user has read access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1673 +msgid "Default user has write access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1674 +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1677 +msgid "Default user has read access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1678 +msgid "Default user has write access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1679 +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1682 +msgid "Default user has read access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1683 +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1686 +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 +msgid "Non-admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1698 +msgid "Only admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1699 +msgid "Non-admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1702 +msgid "User registration with manual account activation" +msgstr "" + +#: kallithea/model/db.py:1703 +msgid "User registration with automatic account activation" +msgstr "" + +#: kallithea/model/db.py:2236 +msgid "Not reviewed" +msgstr "" + +#: kallithea/model/db.py:2239 +msgid "Under review" +msgstr "" + +#: kallithea/model/forms.py:57 +msgid "Please enter a login" +msgstr "" + +#: kallithea/model/forms.py:58 +#, python-format +msgid "Enter a value %(min)i characters long or more" +msgstr "" + +#: kallithea/model/forms.py:66 +msgid "Please enter a password" +msgstr "" + +#: kallithea/model/forms.py:67 +#, python-format +msgid "Enter %(min)i characters or more" +msgstr "" + +#: kallithea/model/forms.py:165 +msgid "Name must not contain only digits" +msgstr "" + +#: kallithea/model/notification.py:254 +#, python-format +msgid "%(user)s commented on changeset %(age)s" +msgstr "" + +#: kallithea/model/notification.py:255 +#, python-format +msgid "%(user)s sent message %(age)s" +msgstr "" + +#: kallithea/model/notification.py:256 +#, python-format +msgid "%(user)s mentioned you %(age)s" +msgstr "" + +#: kallithea/model/notification.py:257 +#, python-format +msgid "%(user)s registered in Kallithea %(age)s" +msgstr "" + +#: kallithea/model/notification.py:258 +#, python-format +msgid "%(user)s opened new pull request %(age)s" +msgstr "" + +#: kallithea/model/notification.py:259 +#, python-format +msgid "%(user)s commented on pull request %(age)s" +msgstr "" + +#: kallithea/model/notification.py:266 +#, python-format +msgid "%(user)s commented on changeset at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:267 +#, python-format +msgid "%(user)s sent message at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:268 +#, python-format +msgid "%(user)s mentioned you at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:269 +#, python-format +msgid "%(user)s registered in Kallithea at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:270 +#, python-format +msgid "%(user)s opened new pull request at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:271 +#, python-format +msgid "%(user)s commented on pull request at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:302 +#, python-format +msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s" +msgstr "" + +#: kallithea/model/notification.py:305 +#, python-format +msgid "New user %(new_username)s registered" +msgstr "" + +#: kallithea/model/notification.py:307 +#, python-format +msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" +msgstr "" + +#: kallithea/model/notification.py:308 +#, python-format +msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" +msgstr "" + +#: kallithea/model/notification.py:321 +msgid "Closing" +msgstr "" + +#: kallithea/model/pull_request.py:137 +#, python-format +msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" +msgstr "" + +#: kallithea/model/scm.py:708 +msgid "latest tip" +msgstr "" + +#: kallithea/model/user.py:192 +msgid "New user registration" +msgstr "" + +#: kallithea/model/user.py:256 +msgid "You can't remove this user since it is crucial for the entire application" +msgstr "" + +#: kallithea/model/user.py:261 +#, python-format +msgid "" +"User \"%s\" still owns %s repositories and cannot be removed. Switch " +"owners or remove those repositories: %s" +msgstr "" + +#: kallithea/model/user.py:266 +#, python-format +msgid "" +"User \"%s\" still owns %s repository groups and cannot be removed. Switch" +" owners or remove those repository groups: %s" +msgstr "" + +#: kallithea/model/user.py:273 +#, python-format +msgid "" +"User \"%s\" still owns %s user groups and cannot be removed. Switch " +"owners or remove those user groups: %s" +msgstr "" + +#: kallithea/model/user.py:368 +msgid "Password reset link" +msgstr "" + +#: kallithea/model/user.py:418 +msgid "Password reset notification" +msgstr "" + +#: kallithea/model/user.py:419 +#, python-format +msgid "" +"The password to your account %s has been changed using password reset " +"form." +msgstr "" + +#: kallithea/model/validators.py:77 kallithea/model/validators.py:78 +msgid "Value cannot be an empty list" +msgstr "" + +#: kallithea/model/validators.py:96 +#, python-format +msgid "Username \"%(username)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:98 +#, python-format +msgid "Username \"%(username)s\" cannot be used" +msgstr "" + +#: kallithea/model/validators.py:100 +msgid "" +"Username may only contain alphanumeric characters underscores, periods or" +" dashes and must begin with an alphanumeric character or underscore" +msgstr "" + +#: kallithea/model/validators.py:127 +msgid "The input is not valid" +msgstr "" + +#: kallithea/model/validators.py:134 +#, python-format +msgid "Username %(username)s is not valid" +msgstr "" + +#: kallithea/model/validators.py:154 +msgid "Invalid user group name" +msgstr "" + +#: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 +msgid "" +"user group name may only contain alphanumeric characters underscores, " +"periods or dashes and must begin with alphanumeric character" +msgstr "" + +#: kallithea/model/validators.py:197 +msgid "Cannot assign this group as parent" +msgstr "" + +#: kallithea/model/validators.py:198 +#, python-format +msgid "Group \"%(group_name)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:200 +#, python-format +msgid "Repository with name \"%(group_name)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:258 +msgid "Invalid characters (non-ascii) in password" +msgstr "" + +#: kallithea/model/validators.py:273 +msgid "Invalid old password" +msgstr "" + +#: kallithea/model/validators.py:289 +msgid "Passwords do not match" +msgstr "" + +#: kallithea/model/validators.py:304 +msgid "Invalid username or password" +msgstr "" + +#: kallithea/model/validators.py:335 +msgid "Token mismatch" +msgstr "" + +#: kallithea/model/validators.py:351 +#, python-format +msgid "Repository name %(repo)s is not allowed" +msgstr "" + +#: kallithea/model/validators.py:353 +#, python-format +msgid "Repository named %(repo)s already exists" +msgstr "" + +#: kallithea/model/validators.py:354 +#, python-format +msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" +msgstr "" + +#: kallithea/model/validators.py:356 +#, python-format +msgid "Repository group with name \"%(repo)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:470 +msgid "Invalid repository URL" +msgstr "" + +#: kallithea/model/validators.py:471 +msgid "" +"Invalid repository URL. It must be a valid http, https, ssh, svn+http or " +"svn+https URL" +msgstr "" + +#: kallithea/model/validators.py:496 +msgid "Fork has to be the same type as parent" +msgstr "" + +#: kallithea/model/validators.py:511 +msgid "You don't have permissions to create repository in this group" +msgstr "" + +#: kallithea/model/validators.py:513 +msgid "no permission to create repository in root location" +msgstr "" + +#: kallithea/model/validators.py:563 +msgid "You don't have permissions to create a group in this location" +msgstr "" + +#: kallithea/model/validators.py:604 +msgid "This username or user group name is not valid" +msgstr "" + +#: kallithea/model/validators.py:697 +msgid "This is not a valid path" +msgstr "" + +#: kallithea/model/validators.py:714 +msgid "This email address is already in use" +msgstr "" + +#: kallithea/model/validators.py:734 +#, python-format +msgid "Email address \"%(email)s\" not found" +msgstr "" + +#: kallithea/model/validators.py:771 +msgid "" +"The LDAP Login attribute of the CN must be specified - this is the name " +"of the attribute that is equivalent to \"username\"" +msgstr "" + +#: kallithea/model/validators.py:783 +msgid "Please enter a valid IPv4 or IPv6 address" +msgstr "" + +#: kallithea/model/validators.py:784 +#, python-format +msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" +msgstr "" + +#: kallithea/model/validators.py:817 +msgid "Key name can only consist of letters, underscore, dash or numbers" +msgstr "" + +#: kallithea/model/validators.py:831 +msgid "Filename cannot be inside a directory" +msgstr "" + +#: kallithea/model/validators.py:847 +#, python-format +msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" +msgstr "" + +#: kallithea/templates/about.html:4 kallithea/templates/about.html:17 +msgid "About" +msgstr "" + +#: kallithea/templates/index.html:5 +msgid "Dashboard" +msgstr "" + +#: kallithea/templates/index_base.html:6 +#: kallithea/templates/admin/my_account/my_account_repos.html:3 +#: kallithea/templates/admin/my_account/my_account_watched.html:3 +#: kallithea/templates/admin/repo_groups/repo_groups.html:9 +#: kallithea/templates/admin/repos/repos.html:9 +#: kallithea/templates/admin/user_groups/user_groups.html:9 +#: kallithea/templates/admin/users/users.html:9 +#: kallithea/templates/bookmarks/bookmarks.html:9 +#: kallithea/templates/branches/branches.html:9 +#: kallithea/templates/journal/journal.html:9 +#: kallithea/templates/journal/journal.html:48 +#: kallithea/templates/journal/journal.html:49 +#: kallithea/templates/tags/tags.html:9 +msgid "quick filter..." +msgstr "" + +#: kallithea/templates/index_base.html:6 +msgid "repositories" +msgstr "" + +#: kallithea/templates/index_base.html:20 +#: kallithea/templates/index_base.html:25 +#: kallithea/templates/admin/repos/repo_add.html:5 +#: kallithea/templates/admin/repos/repo_add.html:19 +#: kallithea/templates/admin/repos/repos.html:22 +msgid "Add Repository" +msgstr "" + +#: kallithea/templates/index_base.html:22 +#: kallithea/templates/index_base.html:27 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:5 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:13 +#: kallithea/templates/admin/repo_groups/repo_groups.html:26 +msgid "Add Repository Group" +msgstr "" + +#: kallithea/templates/index_base.html:32 +msgid "You have admin right to this group, and can edit it" +msgstr "" + +#: kallithea/templates/index_base.html:32 +msgid "Edit Repository Group" +msgstr "" + +#: kallithea/templates/index_base.html:45 +msgid "Group Name" +msgstr "" + +#: kallithea/templates/index_base.html:46 +#: kallithea/templates/index_base.html:127 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:64 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:42 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17 +#: kallithea/templates/admin/repo_groups/repo_groups.html:47 +#: kallithea/templates/admin/repos/repo_add_base.html:28 +#: kallithea/templates/admin/repos/repo_edit_settings.html:65 +#: kallithea/templates/admin/repos/repos.html:48 +#: kallithea/templates/admin/user_groups/user_group_add.html:40 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15 +#: kallithea/templates/admin/user_groups/user_groups.html:47 +#: kallithea/templates/admin/users/user_edit_api_keys.html:64 +#: kallithea/templates/email_templates/changeset_comment.html:18 +#: kallithea/templates/email_templates/pull_request.html:12 +#: kallithea/templates/forks/fork.html:38 +#: kallithea/templates/pullrequests/pullrequest.html:40 +#: kallithea/templates/pullrequests/pullrequest_show.html:38 +#: kallithea/templates/pullrequests/pullrequest_show.html:63 +#: kallithea/templates/summary/summary.html:85 +msgid "Description" +msgstr "" + +#: kallithea/templates/index_base.html:125 +#: kallithea/templates/admin/my_account/my_account_repos.html:46 +#: kallithea/templates/admin/my_account/my_account_watched.html:46 +#: kallithea/templates/admin/repo_groups/repo_groups.html:46 +#: kallithea/templates/admin/repos/repo_add_base.html:9 +#: kallithea/templates/admin/repos/repo_edit_settings.html:7 +#: kallithea/templates/admin/repos/repos.html:47 +#: kallithea/templates/admin/user_groups/user_groups.html:46 +#: kallithea/templates/base/perms_summary.html:53 +#: kallithea/templates/bookmarks/bookmarks.html:49 +#: kallithea/templates/bookmarks/bookmarks_data.html:7 +#: kallithea/templates/branches/branches.html:49 +#: kallithea/templates/branches/branches_data.html:7 +#: kallithea/templates/files/files_browser.html:60 +#: kallithea/templates/journal/journal.html:187 +#: kallithea/templates/journal/journal.html:278 +#: kallithea/templates/tags/tags.html:49 +#: kallithea/templates/tags/tags_data.html:7 +msgid "Name" +msgstr "" + +#: kallithea/templates/index_base.html:128 +msgid "Last Change" +msgstr "" + +#: kallithea/templates/index_base.html:130 +#: kallithea/templates/admin/my_account/my_account_repos.html:48 +#: kallithea/templates/admin/my_account/my_account_watched.html:48 +#: kallithea/templates/admin/repos/repos.html:49 +#: kallithea/templates/journal/journal.html:189 +#: kallithea/templates/journal/journal.html:280 +msgid "Tip" +msgstr "" + +#: kallithea/templates/index_base.html:132 +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10 +#: kallithea/templates/admin/repo_groups/repo_groups.html:49 +#: kallithea/templates/admin/repos/repo_edit_settings.html:53 +#: kallithea/templates/admin/repos/repos.html:50 +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8 +#: kallithea/templates/admin/user_groups/user_groups.html:50 +#: kallithea/templates/pullrequests/pullrequest_data.html:16 +#: kallithea/templates/pullrequests/pullrequest_show.html:156 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 +#: kallithea/templates/summary/summary.html:134 +msgid "Owner" +msgstr "" + +#: kallithea/templates/index_base.html:140 +#: kallithea/templates/admin/my_account/my_account_repos.html:57 +#: kallithea/templates/admin/my_account/my_account_watched.html:57 +#: kallithea/templates/base/root.html:43 +#: kallithea/templates/bookmarks/bookmarks.html:79 +#: kallithea/templates/branches/branches.html:79 +#: kallithea/templates/journal/journal.html:198 +#: kallithea/templates/journal/journal.html:289 +#: kallithea/templates/tags/tags.html:79 +msgid "Click to sort ascending" +msgstr "" + +#: kallithea/templates/index_base.html:141 +#: kallithea/templates/admin/my_account/my_account_repos.html:58 +#: kallithea/templates/admin/my_account/my_account_watched.html:58 +#: kallithea/templates/base/root.html:44 +#: kallithea/templates/bookmarks/bookmarks.html:80 +#: kallithea/templates/branches/branches.html:80 +#: kallithea/templates/journal/journal.html:199 +#: kallithea/templates/journal/journal.html:290 +#: kallithea/templates/tags/tags.html:80 +msgid "Click to sort descending" +msgstr "" + +#: kallithea/templates/index_base.html:142 +msgid "No repositories found." +msgstr "" + +#: kallithea/templates/index_base.html:143 +#: kallithea/templates/admin/my_account/my_account_repos.html:60 +#: kallithea/templates/admin/my_account/my_account_watched.html:60 +#: kallithea/templates/base/root.html:46 +#: kallithea/templates/bookmarks/bookmarks.html:82 +#: kallithea/templates/branches/branches.html:82 +#: kallithea/templates/journal/journal.html:201 +#: kallithea/templates/journal/journal.html:292 +#: kallithea/templates/tags/tags.html:82 +msgid "Data error." +msgstr "" + +#: kallithea/templates/index_base.html:144 +#: kallithea/templates/admin/my_account/my_account_repos.html:61 +#: kallithea/templates/admin/my_account/my_account_watched.html:61 +#: kallithea/templates/base/root.html:47 +#: kallithea/templates/bookmarks/bookmarks.html:83 +#: kallithea/templates/branches/branches.html:83 +#: kallithea/templates/journal/journal.html:202 +#: kallithea/templates/journal/journal.html:293 +#: kallithea/templates/tags/tags.html:83 +msgid "Loading..." +msgstr "" + +#: kallithea/templates/login.html:5 kallithea/templates/login.html:15 +#: kallithea/templates/base/base.html:414 +msgid "Log In" +msgstr "" + +#: kallithea/templates/login.html:13 +#, python-format +msgid "Log In to %s" +msgstr "" + +#: kallithea/templates/login.html:26 kallithea/templates/register.html:24 +#: kallithea/templates/admin/admin_log.html:5 +#: kallithea/templates/admin/my_account/my_account_profile.html:25 +#: kallithea/templates/admin/users/user_add.html:32 +#: kallithea/templates/admin/users/user_edit_profile.html:24 +#: kallithea/templates/admin/users/users.html:50 +#: kallithea/templates/base/base.html:390 +#: kallithea/templates/pullrequests/pullrequest_show.html:166 +msgid "Username" +msgstr "" + +#: kallithea/templates/login.html:33 kallithea/templates/register.html:33 +#: kallithea/templates/admin/my_account/my_account.html:37 +#: kallithea/templates/admin/users/user_add.html:41 +#: kallithea/templates/base/base.html:399 +msgid "Password" +msgstr "" + +#: kallithea/templates/login.html:44 +msgid "Remember me" +msgstr "" + +#: kallithea/templates/login.html:53 +msgid "Forgot your password ?" +msgstr "" + +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 +msgid "Don't have an account ?" +msgstr "" + +#: kallithea/templates/login.html:59 +msgid "Sign In" +msgstr "" + +#: kallithea/templates/password_reset.html:5 +msgid "Password Reset" +msgstr "" + +#: kallithea/templates/password_reset.html:12 +#: kallithea/templates/password_reset_confirmation.html:12 +#, python-format +msgid "Reset Your Password to %s" +msgstr "" + +#: kallithea/templates/password_reset.html:14 +#: kallithea/templates/password_reset_confirmation.html:5 +#: kallithea/templates/password_reset_confirmation.html:14 +msgid "Reset Your Password" +msgstr "" + +#: kallithea/templates/password_reset.html:25 +msgid "Email Address" +msgstr "" + +#: kallithea/templates/password_reset.html:35 +#: kallithea/templates/register.html:79 +msgid "Captcha" +msgstr "" + +#: kallithea/templates/password_reset.html:46 +msgid "Send Password Reset Email" +msgstr "" + +#: kallithea/templates/password_reset.html:47 +msgid "" +"A password reset link will be sent to the specified email address if it " +"is registered in the system." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:19 +#, python-format +msgid "You are about to set a new password for the email address %s." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:20 +msgid "" +"Note that you must use the same browser session for this as the one used " +"to request the password reset." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:30 +msgid "Code you received in the email" +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:39 +msgid "New Password" +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:48 +msgid "Confirm New Password" +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:56 +msgid "Confirm" +msgstr "" + +#: kallithea/templates/register.html:5 kallithea/templates/register.html:14 +#: kallithea/templates/register.html:90 +msgid "Sign Up" +msgstr "" + +#: kallithea/templates/register.html:12 +#, python-format +msgid "Sign Up to %s" +msgstr "" + +#: kallithea/templates/register.html:42 +msgid "Re-enter password" +msgstr "" + +#: kallithea/templates/register.html:51 +#: kallithea/templates/admin/my_account/my_account_profile.html:34 +#: kallithea/templates/admin/users/user_add.html:59 +#: kallithea/templates/admin/users/user_edit_profile.html:78 +#: kallithea/templates/admin/users/users.html:51 +msgid "First Name" +msgstr "" + +#: kallithea/templates/register.html:60 +#: kallithea/templates/admin/my_account/my_account_profile.html:43 +#: kallithea/templates/admin/users/user_add.html:68 +#: kallithea/templates/admin/users/user_edit_profile.html:87 +#: kallithea/templates/admin/users/users.html:52 +msgid "Last Name" +msgstr "" + +#: kallithea/templates/register.html:69 +#: kallithea/templates/admin/my_account/my_account_profile.html:52 +#: kallithea/templates/admin/settings/settings.html:31 +#: kallithea/templates/admin/users/user_add.html:77 +#: kallithea/templates/admin/users/user_edit_profile.html:33 +msgid "Email" +msgstr "" + +#: kallithea/templates/register.html:92 +msgid "Registered accounts are ready to use and need no further action." +msgstr "" + +#: kallithea/templates/register.html:94 +msgid "Please wait for an administrator to activate your account." +msgstr "" + +#: kallithea/templates/switch_to_list.html:10 +#: kallithea/templates/branches/branches_data.html:69 +msgid "There are no branches yet" +msgstr "" + +#: kallithea/templates/switch_to_list.html:32 +#: kallithea/templates/tags/tags_data.html:44 +msgid "There are no tags yet" +msgstr "" + +#: kallithea/templates/switch_to_list.html:45 +#: kallithea/templates/bookmarks/bookmarks_data.html:43 +msgid "There are no bookmarks yet" +msgstr "" + +#: kallithea/templates/admin/admin.html:5 +#: kallithea/templates/admin/admin.html:13 +#: kallithea/templates/base/base.html:59 +msgid "Admin Journal" +msgstr "" + +#: kallithea/templates/admin/admin.html:10 +msgid "journal filter..." +msgstr "" + +#: kallithea/templates/admin/admin.html:12 +#: kallithea/templates/journal/journal.html:11 +msgid "Filter" +msgstr "" + +#: kallithea/templates/admin/admin.html:13 +#: kallithea/templates/journal/journal.html:12 +#, python-format +msgid "%s Entry" +msgid_plural "%s Entries" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/admin/admin_log.html:6 +#: kallithea/templates/admin/my_account/my_account_repos.html:50 +#: kallithea/templates/admin/my_account/my_account_watched.html:50 +#: kallithea/templates/admin/repo_groups/repo_groups.html:50 +#: kallithea/templates/admin/repos/repo_edit_fields.html:8 +#: kallithea/templates/admin/repos/repos.html:52 +#: kallithea/templates/admin/user_groups/user_groups.html:51 +#: kallithea/templates/admin/users/users.html:57 +#: kallithea/templates/journal/journal.html:191 +#: kallithea/templates/journal/journal.html:282 +msgid "Action" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:7 +#: kallithea/templates/admin/permissions/permissions_globals.html:18 +msgid "Repository" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:8 +#: kallithea/templates/bookmarks/bookmarks.html:51 +#: kallithea/templates/bookmarks/bookmarks_data.html:9 +#: kallithea/templates/branches/branches.html:51 +#: kallithea/templates/branches/branches_data.html:9 +#: kallithea/templates/tags/tags.html:51 +#: kallithea/templates/tags/tags_data.html:9 +msgid "Date" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:9 +msgid "From IP" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:63 +msgid "No actions yet" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:5 +msgid "Authentication Settings" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:11 +#: kallithea/templates/base/base.html:65 +msgid "Authentication" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:28 +msgid "Authentication Plugins" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:31 +msgid "Enabled Plugins" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:33 +msgid "" +"Comma-separated list of plugins; Kallithea will try user authentication " +"in plugin order" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:34 +msgid "Available built-in plugins" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:51 +msgid "Plugin" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:101 +#: kallithea/templates/admin/defaults/defaults.html:82 +#: kallithea/templates/admin/my_account/my_account_password.html:36 +#: kallithea/templates/admin/my_account/my_account_profile.html:60 +#: kallithea/templates/admin/permissions/permissions_globals.html:112 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:69 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:101 +#: kallithea/templates/admin/repos/repo_edit_settings.html:127 +#: kallithea/templates/admin/settings/settings_hooks.html:53 +#: kallithea/templates/admin/user_groups/user_group_add.html:57 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60 +#: kallithea/templates/admin/users/user_add.html:96 +#: kallithea/templates/admin/users/user_edit_profile.html:113 +#: kallithea/templates/base/default_perms_box.html:64 +msgid "Save" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:5 +#: kallithea/templates/admin/defaults/defaults.html:11 +#: kallithea/templates/base/base.html:66 +msgid "Repository Defaults" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:33 +#: kallithea/templates/admin/repos/repo_add_base.html:55 +#: kallithea/templates/admin/repos/repo_edit_fields.html:7 +msgid "Type" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:42 +#: kallithea/templates/admin/repos/repo_add_base.html:73 +#: kallithea/templates/admin/repos/repo_edit_settings.html:75 +#: kallithea/templates/data_table/_dt_elements.html:72 +msgid "Private repository" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:46 +#: kallithea/templates/admin/repos/repo_add_base.html:77 +#: kallithea/templates/admin/repos/repo_edit_settings.html:79 +#: kallithea/templates/forks/fork.html:72 +msgid "" +"Private repositories are only visible to people explicitly added as " +"collaborators." +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:53 +#: kallithea/templates/admin/repos/repo_edit_settings.html:84 +msgid "Enable statistics" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:57 +#: kallithea/templates/admin/repos/repo_edit_settings.html:88 +msgid "Enable statistics window on summary page." +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:63 +#: kallithea/templates/admin/repos/repo_edit_settings.html:93 +msgid "Enable downloads" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:67 +#: kallithea/templates/admin/repos/repo_edit_settings.html:97 +msgid "Enable download menu on summary page." +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:73 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34 +#: kallithea/templates/admin/repos/repo_edit_settings.html:102 +msgid "Enable locking" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:77 +#: kallithea/templates/admin/repos/repo_edit_settings.html:106 +msgid "Enable lock-by-pulling on repository." +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:5 +#: kallithea/templates/admin/gists/edit.html:18 +msgid "Edit Gist" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:36 +#, python-format +msgid "" +"Gist was update since you started editing. Copy your changes and click " +"%(here)s to reload new version." +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:55 +#: kallithea/templates/admin/gists/new.html:39 +msgid "Gist description ..." +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:57 +#: kallithea/templates/admin/gists/new.html:41 +msgid "Gist lifetime" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:61 +#: kallithea/templates/admin/gists/edit.html:63 +#: kallithea/templates/admin/gists/index.html:57 +#: kallithea/templates/admin/gists/index.html:59 +#: kallithea/templates/admin/gists/show.html:47 +#: kallithea/templates/admin/gists/show.html:49 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:8 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:27 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:32 +#: kallithea/templates/admin/users/user_edit_api_keys.html:8 +#: kallithea/templates/admin/users/user_edit_api_keys.html:27 +#: kallithea/templates/admin/users/user_edit_api_keys.html:32 +msgid "Expires" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:61 +#: kallithea/templates/admin/gists/index.html:57 +#: kallithea/templates/admin/gists/show.html:47 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:8 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:27 +#: kallithea/templates/admin/users/user_edit_api_keys.html:8 +#: kallithea/templates/admin/users/user_edit_api_keys.html:27 +msgid "Never" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:146 +msgid "Update Gist" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 +msgid "Cancel" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:6 +#: kallithea/templates/admin/gists/index.html:16 +#, python-format +msgid "Private Gists for User %s" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:8 +#: kallithea/templates/admin/gists/index.html:18 +#, python-format +msgid "Public Gists for User %s" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:10 +#: kallithea/templates/admin/gists/index.html:20 +msgid "Public Gists" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:37 +#: kallithea/templates/admin/gists/show.html:25 +#: kallithea/templates/base/base.html:321 +msgid "Create New Gist" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:54 +#: kallithea/templates/data_table/_dt_elements.html:141 +msgid "Created" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:74 +msgid "There are no gists yet" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:5 +#: kallithea/templates/admin/gists/new.html:18 +msgid "New Gist" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:47 +msgid "name this file..." +msgstr "" + +#: kallithea/templates/admin/gists/new.html:56 +msgid "Create Private Gist" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:57 +msgid "Create Public Gist" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:58 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:15 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:70 +#: kallithea/templates/admin/my_account/my_account_emails.html:46 +#: kallithea/templates/admin/my_account/my_account_password.html:37 +#: kallithea/templates/admin/my_account/my_account_profile.html:61 +#: kallithea/templates/admin/permissions/permissions_globals.html:113 +#: kallithea/templates/admin/permissions/permissions_ips.html:39 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43 +#: kallithea/templates/admin/repos/repo_edit_fields.html:59 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:102 +#: kallithea/templates/admin/repos/repo_edit_settings.html:128 +#: kallithea/templates/admin/settings/settings_global.html:57 +#: kallithea/templates/admin/settings/settings_vcs.html:81 +#: kallithea/templates/admin/settings/settings_visual.html:117 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105 +#: kallithea/templates/admin/users/user_edit_api_keys.html:15 +#: kallithea/templates/admin/users/user_edit_api_keys.html:70 +#: kallithea/templates/admin/users/user_edit_emails.html:46 +#: kallithea/templates/admin/users/user_edit_ips.html:50 +#: kallithea/templates/admin/users/user_edit_profile.html:114 +#: kallithea/templates/base/default_perms_box.html:65 +#: kallithea/templates/files/files_add.html:65 +#: kallithea/templates/files/files_delete.html:44 +#: kallithea/templates/files/files_edit.html:68 +#: kallithea/templates/pullrequests/pullrequest.html:89 +msgid "Reset" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:5 +#: kallithea/templates/admin/gists/show.html:9 +msgid "Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:10 +#: kallithea/templates/email_templates/changeset_comment.html:15 +#: kallithea/templates/email_templates/pull_request.html:10 +#: kallithea/templates/email_templates/pull_request_comment.html:15 +msgid "URL" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:37 +msgid "Public Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:39 +msgid "Private Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:56 +#: kallithea/templates/admin/my_account/my_account_emails.html:19 +#: kallithea/templates/admin/permissions/permissions_ips.html:12 +#: kallithea/templates/admin/repos/repo_edit_advanced.html:75 +#: kallithea/templates/admin/repos/repo_edit_fields.html:18 +#: kallithea/templates/admin/settings/settings_hooks.html:36 +#: kallithea/templates/admin/users/user_edit_emails.html:19 +#: kallithea/templates/admin/users/user_edit_ips.html:22 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 +#: kallithea/templates/data_table/_dt_elements.html:129 +#: kallithea/templates/data_table/_dt_elements.html:157 +#: kallithea/templates/data_table/_dt_elements.html:173 +#: kallithea/templates/data_table/_dt_elements.html:189 +#: kallithea/templates/files/files_source.html:39 +#: kallithea/templates/files/files_source.html:42 +#: kallithea/templates/files/files_source.html:45 +#: kallithea/templates/pullrequests/pullrequest_data.html:20 +msgid "Delete" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:56 +msgid "Confirm to delete this Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:63 +#: kallithea/templates/base/perms_summary.html:43 +#: kallithea/templates/base/perms_summary.html:79 +#: kallithea/templates/base/perms_summary.html:81 +#: kallithea/templates/data_table/_dt_elements.html:122 +#: kallithea/templates/data_table/_dt_elements.html:123 +#: kallithea/templates/data_table/_dt_elements.html:150 +#: kallithea/templates/data_table/_dt_elements.html:151 +#: kallithea/templates/data_table/_dt_elements.html:165 +#: kallithea/templates/data_table/_dt_elements.html:167 +#: kallithea/templates/data_table/_dt_elements.html:181 +#: kallithea/templates/data_table/_dt_elements.html:183 +#: kallithea/templates/files/diff_2way.html:56 +#: kallithea/templates/files/files_source.html:41 +#: kallithea/templates/files/files_source.html:44 +#: kallithea/templates/pullrequests/pullrequest_show.html:41 +msgid "Edit" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:65 +#: kallithea/templates/files/files_edit.html:49 +#: kallithea/templates/files/files_source.html:34 +msgid "Show as Raw" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:73 +msgid "created" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:86 +msgid "Show as raw" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:5 +#: kallithea/templates/admin/my_account/my_account.html:9 +#: kallithea/templates/base/base.html:431 +msgid "My Account" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:35 +#: kallithea/templates/admin/users/user_edit.html:29 +msgid "Profile" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:36 +msgid "Email Addresses" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:38 +#: kallithea/templates/admin/users/user_edit.html:31 +msgid "API Keys" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:39 +msgid "Owned Repositories" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:40 +#: kallithea/templates/journal/journal.html:53 +msgid "Watched Repositories" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:41 +#: kallithea/templates/admin/permissions/permissions.html:30 +#: kallithea/templates/admin/user_groups/user_group_edit.html:32 +#: kallithea/templates/admin/users/user_edit.html:34 +msgid "Show Permissions" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:6 +#: kallithea/templates/admin/users/user_edit_api_keys.html:6 +msgid "Built-in" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:14 +#: kallithea/templates/admin/users/user_edit_api_keys.html:14 +#, python-format +msgid "Confirm to reset this API key: %s" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:30 +#: kallithea/templates/admin/users/user_edit_api_keys.html:30 +msgid "Expired" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:40 +#: kallithea/templates/admin/users/user_edit_api_keys.html:40 +#, python-format +msgid "Confirm to remove this API key: %s" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:42 +#: kallithea/templates/admin/users/user_edit_api_keys.html:42 +msgid "Remove" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:49 +#: kallithea/templates/admin/users/user_edit_api_keys.html:49 +msgid "No additional API keys specified" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:61 +#: kallithea/templates/admin/users/user_edit_api_keys.html:61 +msgid "New API key" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:69 +#: kallithea/templates/admin/my_account/my_account_emails.html:45 +#: kallithea/templates/admin/permissions/permissions_ips.html:38 +#: kallithea/templates/admin/repos/repo_add_base.html:81 +#: kallithea/templates/admin/repos/repo_edit_fields.html:58 +#: kallithea/templates/admin/users/user_edit_api_keys.html:69 +#: kallithea/templates/admin/users/user_edit_emails.html:45 +#: kallithea/templates/admin/users/user_edit_ips.html:49 +msgid "Add" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:7 +#: kallithea/templates/admin/users/user_edit_emails.html:7 +msgid "Primary" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:20 +#: kallithea/templates/admin/users/user_edit_emails.html:20 +#, python-format +msgid "Confirm to delete this email: %s" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:26 +#: kallithea/templates/admin/users/user_edit_emails.html:26 +msgid "No additional emails specified." +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:38 +#: kallithea/templates/admin/users/user_edit_emails.html:38 +msgid "New email address" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:1 +msgid "Change Your Account Password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:10 +msgid "Current password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:19 +#: kallithea/templates/admin/users/user_edit_profile.html:60 +msgid "New password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:28 +msgid "Confirm new password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:45 +#, python-format +msgid "This account is managed with %s and the password cannot be changed here" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:11 +msgid "Change your avatar at" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:12 +#: kallithea/templates/admin/users/user_edit_profile.html:9 +msgid "Using" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:14 +#: kallithea/templates/admin/users/user_edit_profile.html:11 +msgid "Avatars are disabled" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:15 +msgid "Missing email, please update your user email address." +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:16 +#: kallithea/templates/admin/users/user_edit_profile.html:15 +msgid "Current IP" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_repos.html:1 +msgid "Repositories You Own" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_repos.html:59 +#: kallithea/templates/admin/my_account/my_account_watched.html:59 +#: kallithea/templates/base/root.html:45 +#: kallithea/templates/bookmarks/bookmarks.html:81 +#: kallithea/templates/branches/branches.html:81 +#: kallithea/templates/journal/journal.html:200 +#: kallithea/templates/journal/journal.html:291 +#: kallithea/templates/tags/tags.html:81 +msgid "No records found." +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_watched.html:1 +msgid "Repositories You are Watching" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:5 +#: kallithea/templates/admin/notifications/notifications.html:9 +msgid "My Notifications" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:24 +msgid "All" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:25 +msgid "Comments" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:26 +#: kallithea/templates/base/base.html:180 +msgid "Pull Requests" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:30 +msgid "Mark All Read" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications_data.html:40 +msgid "No notifications here yet" +msgstr "" + +#: kallithea/templates/admin/notifications/show_notification.html:5 +#: kallithea/templates/admin/notifications/show_notification.html:11 +msgid "Show Notification" +msgstr "" + +#: kallithea/templates/admin/notifications/show_notification.html:9 +#: kallithea/templates/base/base.html:430 +msgid "Notifications" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions.html:5 +#: kallithea/templates/admin/permissions/permissions.html:11 +#: kallithea/templates/base/base.html:64 +msgid "Default Permissions" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions.html:28 +#: kallithea/templates/admin/settings/settings.html:29 +msgid "Global" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions.html:29 +#: kallithea/templates/admin/users/user_edit.html:32 +msgid "IP Whitelist" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:7 +msgid "Anonymous access" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:13 +#, python-format +msgid "" +"Allow access to Kallithea without needing to log in. Anonymous users use " +"%s user permissions." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:25 +msgid "" +"All default permissions on each repository will be reset to chosen " +"permission, note that all custom default permission on repositories will " +"be lost" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:26 +msgid "Apply to all existing repositories" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:27 +msgid "Permissions for the Default user on new repositories." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:32 +#: kallithea/templates/admin/repos/repo_add_base.html:37 +#: kallithea/templates/admin/repos/repo_edit_settings.html:35 +#: kallithea/templates/data_table/_dt_elements.html:202 +#: kallithea/templates/forks/fork.html:48 +msgid "Repository group" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:39 +msgid "" +"All default permissions on each repository group will be reset to chosen " +"permission, note that all custom default permission on repository groups " +"will be lost" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:40 +msgid "Apply to all existing repository groups" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:41 +msgid "Permissions for the Default user on new repository groups." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:46 +#: kallithea/templates/data_table/_dt_elements.html:209 +msgid "User group" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:53 +msgid "" +"All default permissions on each user group will be reset to chosen " +"permission, note that all custom default permission on user groups will " +"be lost" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:54 +msgid "Apply to all existing user groups" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:55 +msgid "Permissions for the Default user on new user groups." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:60 +msgid "Top level repository creation" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:64 +msgid "Enable this to allow non-admins to create repositories at the top level." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:65 +msgid "" +"Note: This will also give all users API access to create repositories " +"everywhere. That might change in future versions." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:70 +msgid "Repository creation with group write access" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:74 +msgid "" +"With this, write permission to a repository group allows creating " +"repositories inside that group. Without this, group write permissions " +"mean nothing." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:79 +msgid "User group creation" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:83 +msgid "Enable this to allow non-admins to create user groups." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:88 +msgid "Repository forking" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:92 +msgid "Enable this to allow non-admins to fork repositories." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:97 +msgid "Registration" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:105 +msgid "External auth account activation" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_ips.html:13 +#: kallithea/templates/admin/users/user_edit_ips.html:23 +#, python-format +msgid "Confirm to delete this IP address: %s" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_ips.html:19 +#: kallithea/templates/admin/users/user_edit_ips.html:30 +msgid "All IP addresses are allowed." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_ips.html:30 +#: kallithea/templates/admin/users/user_edit_ips.html:42 +msgid "New IP address" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:11 +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:11 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105 +#: kallithea/templates/admin/repo_groups/repo_groups.html:10 +#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80 +msgid "Repository Groups" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:33 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8 +#: kallithea/templates/admin/user_groups/user_group_add.html:32 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7 +msgid "Group name" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:51 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26 +msgid "Group parent" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:60 +#: kallithea/templates/admin/repos/repo_add_base.html:46 +msgid "Copy parent group permissions" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:64 +#: kallithea/templates/admin/repos/repo_add_base.html:50 +msgid "Copy permission set from parent repository group." +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:5 +#, python-format +msgid "%s Repository Group Settings" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21 +msgid "Add Child Group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40 +#: kallithea/templates/admin/repos/repo_edit.html:12 +#: kallithea/templates/admin/repos/repo_edit.html:40 +#: kallithea/templates/admin/settings/settings.html:11 +#: kallithea/templates/admin/user_groups/user_group_edit.html:29 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 +#: kallithea/templates/data_table/_dt_elements.html:45 +#: kallithea/templates/data_table/_dt_elements.html:49 +msgid "Settings" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41 +#: kallithea/templates/admin/repos/repo_edit.html:46 +#: kallithea/templates/admin/user_groups/user_group_edit.html:30 +#: kallithea/templates/admin/users/user_edit.html:33 +msgid "Advanced" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42 +#: kallithea/templates/admin/repos/repo_edit.html:43 +#: kallithea/templates/admin/user_groups/user_group_edit.html:31 +msgid "Permissions" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:1 +#, python-format +msgid "Repository Group: %s" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:6 +msgid "Top level repositories" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7 +msgid "Total repositories" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8 +msgid "Children groups" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9 +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7 +#: kallithea/templates/admin/users/user_edit_advanced.html:8 +#: kallithea/templates/pullrequests/pullrequest_show.html:148 +msgid "Created on" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21 +#: kallithea/templates/data_table/_dt_elements.html:190 +#, python-format +msgid "Confirm to delete this group: %s with %s repository" +msgid_plural "Confirm to delete this group: %s with %s repositories" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25 +msgid "Delete this repository group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:12 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11 +msgid "User/User Group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:24 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:37 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45 +msgid "Default" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:43 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:68 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71 +msgid "Revoke" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:94 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97 +msgid "Add new" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103 +msgid "Apply to children" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107 +msgid "Both" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108 +msgid "" +"Set or revoke permission to all children of that group, including non-" +"private repositories and other groups if selected." +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38 +msgid "" +"Enable lock-by-pulling on group. This option will be applied to all other" +" groups and repositories inside" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53 +msgid "Remove this group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53 +msgid "Confirm to delete this group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_show.html:4 +#, python-format +msgid "%s Repository group dashboard" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_show.html:9 +msgid "Home" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_show.html:13 +msgid "with" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_groups.html:5 +msgid "Repository Groups Administration" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_groups.html:48 +msgid "Number of Top-level Repositories" +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:17 +msgid "Clone remote repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:22 +msgid "" +"Optional: URL of a remote repository. If set, the repository will be " +"created as a clone from this URL." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:32 +#: kallithea/templates/admin/repos/repo_edit_settings.html:69 +#: kallithea/templates/forks/fork.html:42 +msgid "Keep it short and to the point. Use a README file for longer descriptions." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:41 +#: kallithea/templates/admin/repos/repo_edit_settings.html:39 +#: kallithea/templates/forks/fork.html:52 +msgid "Optionally select a group to put this repository into." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:59 +msgid "Type of repository to create." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:64 +#: kallithea/templates/admin/repos/repo_edit_settings.html:44 +#: kallithea/templates/forks/fork.html:58 +msgid "Landing revision" +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:68 +msgid "" +"Default revision for files page, downloads, full text search index and " +"readme generation" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:9 +#, python-format +msgid "%s Creating Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:13 +msgid "Creating repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:27 +#, python-format +msgid "" +"Repository \"%(repo_name)s\" is being created, you will be redirected " +"when this process is finished.repo_name" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:39 +msgid "" +"We're sorry but error occurred during this operation. Please check your " +"Kallithea server logs, or contact administrator." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:8 +#, python-format +msgid "%s Repository Settings" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:49 +msgid "Extra Fields" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:52 +msgid "Caches" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:55 +msgid "Remote" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:58 +#: kallithea/templates/summary/statistics.html:8 +#: kallithea/templates/summary/summary.html:171 +#: kallithea/templates/summary/summary.html:172 +msgid "Statistics" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:1 +msgid "Parent" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:5 +#: kallithea/templates/admin/repos/repo_edit_fork.html:5 +msgid "Set" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:8 +#: kallithea/templates/admin/repos/repo_edit_fork.html:9 +msgid "Manually set this repository as a fork of another from the list." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:22 +msgid "Public Journal Visibility" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:29 +msgid "Remove from public journal" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:34 +msgid "Add to Public Journal" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:40 +msgid "" +"All actions done in this repository will be visible to everyone in the " +"public journal." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:46 +msgid "Change Locking" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:52 +msgid "Confirm to unlock repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:54 +msgid "Unlock Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:60 +msgid "Confirm to lock repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:62 +msgid "Lock Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:64 +msgid "Repository is not locked" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:68 +msgid "" +"Force locking on the repository. Works only when anonymous access is " +"disabled. Triggering a pull locks the repository. The user who is " +"pulling locks the repository; only the user who pulled and locked it can " +"unlock it by doing a push." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:79 +#: kallithea/templates/data_table/_dt_elements.html:130 +#, python-format +msgid "Confirm to delete this repository: %s" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:81 +msgid "Delete this Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:84 +#, python-format +msgid "This repository has %s fork" +msgid_plural "This repository has %s forks" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:85 +msgid "Detach forks" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:86 +msgid "Delete forks" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:90 +msgid "" +"The deleted repository will be moved away and hidden until the " +"administrator expires it. The administrator can both permanently delete " +"it or restore it." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:4 +msgid "Invalidate Repository Cache" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:7 +msgid "" +"Manually invalidate cache for this repository. On first access, the " +"repository will be cached again." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:12 +msgid "List of Cached Values" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:15 +msgid "Prefix" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:16 +#: kallithea/templates/admin/repos/repo_edit_fields.html:6 +msgid "Key" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:17 +#: kallithea/templates/admin/user_groups/user_group_add.html:49 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24 +#: kallithea/templates/admin/user_groups/user_groups.html:49 +#: kallithea/templates/admin/users/user_add.html:86 +#: kallithea/templates/admin/users/user_edit_profile.html:96 +#: kallithea/templates/admin/users/users.html:54 +msgid "Active" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:5 +msgid "Label" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:19 +#, python-format +msgid "Confirm to delete this field: %s" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:33 +msgid "New field key" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:41 +msgid "New field label" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:44 +msgid "Enter short label" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:50 +msgid "New field description" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:53 +msgid "Enter description of a field" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:66 +msgid "Extra fields are disabled." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_permissions.html:21 +msgid "Private Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:3 +msgid "Remote repository URL" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:9 +msgid "Pull Changes from Remote Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:11 +msgid "Confirm to pull changes from remote repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:17 +msgid "This repository does not have a remote repository URL." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:11 +msgid "Permanent Repository ID" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:11 +msgid "What is that?" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:13 +msgid "URL by id" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:14 +msgid "" +"In case this repository is renamed or moved into another group the " +"repository URL changes.\n" +" Using the above permanent URL guarantees " +"that this repository always will be accessible on that URL.\n" +" This is useful for CI systems, or any " +"other cases that you need to hardcode the URL into a 3rd party service." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:21 +msgid "Remote repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:25 +msgid "Repository URL" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:29 +msgid "" +"Optional: URL of a remote repository. If set, the repository can be " +"pulled from this URL." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:48 +msgid "Default revision for files page, downloads, whoosh and readme" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:58 +msgid "Change owner of this repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:6 +msgid "Processed commits" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:7 +msgid "Processed progress" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:10 +msgid "Reset Statistics" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:10 +msgid "Confirm to remove current statistics." +msgstr "" + +#: kallithea/templates/admin/repos/repos.html:5 +msgid "Repositories Administration" +msgstr "" + +#: kallithea/templates/admin/repos/repos.html:51 +msgid "State" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:5 +msgid "Settings Administration" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:27 +msgid "VCS" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:28 +msgid "Remap and Rescan" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:30 +msgid "Visual" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:32 +#: kallithea/templates/admin/settings/settings_vcs.html:19 +msgid "Hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:33 +msgid "Full Text Search" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:34 +msgid "System Info" +msgstr "" + +#: kallithea/templates/admin/settings/settings_email.html:7 +msgid "Send test email to" +msgstr "" + +#: kallithea/templates/admin/settings/settings_email.html:15 +msgid "Send" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:8 +msgid "Site branding" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:12 +msgid "Set a custom title for your Kallithea Service." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:18 +msgid "HTTP authentication realm" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:27 +msgid "Analytics HTML block" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:31 +msgid "" +"HTML with JavaScript for web analytics systems like Google Analytics or " +"Piwik. This will be added at the bottom of every page." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:37 +msgid "ReCaptcha public key" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:41 +msgid "Public key for reCaptcha system." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:47 +msgid "ReCaptcha private key" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:51 +msgid "" +"Private key for reCaptcha system. Setting this value will enable captcha " +"on registration." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:56 +#: kallithea/templates/admin/settings/settings_vcs.html:80 +#: kallithea/templates/admin/settings/settings_visual.html:116 +msgid "Save Settings" +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:1 +msgid "Built-in Mercurial Hooks (Read-Only)" +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:15 +msgid "" +"Hooks can be used to trigger actions on certain events such as push / " +"pull. They can trigger Python functions or external applications." +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:19 +msgid "Custom Hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:67 +msgid "Failed to remove hook" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:6 +msgid "Rescan option" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:11 +msgid "Delete records of missing repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:13 +msgid "" +"Check this option to remove all comments, pull requests and other records" +" related to repositories that no longer exist in the filesystem." +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:17 +msgid "Invalidate cache for all repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:19 +msgid "Check this to reload data and clear cache keys for all repositories." +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:23 +msgid "Install Git hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:25 +msgid "" +"Verify if Kallithea's Git hooks are installed for each repository. " +"Current hooks will be updated to the latest version." +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:28 +msgid "Overwrite existing Git hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:30 +msgid "" +"If installing Git hooks, overwrite any existing hooks, even if they do " +"not seem to come from Kallithea. WARNING: This operation will destroy any" +" custom git hooks you may have deployed by hand!" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:35 +msgid "Rescan Repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:7 +msgid "Index build option" +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:12 +msgid "Build from scratch" +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:15 +msgid "" +"This option completely reindexeses all of the repositories for proper " +"fulltext search capabilities." +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:21 +msgid "Reindex" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:4 +msgid "Kallithea version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:4 +msgid "Check for updates" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:5 +msgid "Kallithea configuration file" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:6 +msgid "Python version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:7 +msgid "Platform" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:8 +msgid "Git version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:9 +msgid "Git path" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:10 +msgid "Upgrade info endpoint" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:10 +msgid "Note: please make sure this server can access this URL" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:15 +msgid "Checking for updates..." +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:23 +msgid "Python Packages" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:6 +msgid "Web" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:11 +msgid "Require SSL for vcs operations" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:13 +msgid "" +"Activate to require SSL both pushing and pulling. If SSL certificate is " +"missing, it will return an HTTP Error 406: Not Acceptable." +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:24 +msgid "Show repository size after push" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:28 +msgid "Log user push commands" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:32 +msgid "Log user pull commands" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:36 +msgid "Update repository after push (hg update)" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:42 +msgid "Mercurial extensions" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:47 +msgid "Enable largefiles extension" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:51 +msgid "Enable hgsubversion extension" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:53 +msgid "" +"Requires hgsubversion library to be installed. Enables cloning of remote " +"Subversion repositories while converting them to Mercurial." +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:64 +msgid "Location of repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:69 +msgid "" +"Click to unlock. You must restart Kallithea in order to make this setting" +" take effect." +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:72 +msgid "" +"Filesystem location where repositories are stored. After changing this " +"value, a restart and rescan of the repository folder are both required." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:8 +msgid "General" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:13 +msgid "Use repository extra fields" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:15 +msgid "Allows storing additional customized fields per repository." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:18 +msgid "Show Kallithea version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:20 +msgid "Shows or hides a version number of Kallithea displayed in the footer." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:24 +msgid "Use Gravatars in Kallithea" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:30 +msgid "" +"Gravatar URL allows you to use another avatar server application.\n" +" The following " +"variables of the URL will be replaced accordingly.\n" +" {scheme} " +"'http' or 'https' sent from running Kallithea server,\n" +" {email} user " +"email,\n" +" {md5email} md5 " +"hash of the user email (like at gravatar.com),\n" +" {size} size " +"of the image that is expected from the server application,\n" +" {netloc} " +"network location/server host of running Kallithea server" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:42 +msgid "" +"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'." +"\n" +" The following " +"variables are available:\n" +" {scheme} 'http' " +"or 'https' sent from running Kallithea server,\n" +" {user} current " +"user username,\n" +" {netloc} network " +"location/server host of running Kallithea server,\n" +" {repo} full " +"repository name,\n" +" {repoid} ID of " +"repository, can be used to contruct clone-by-id" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:55 +msgid "Dashboard items" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:59 +msgid "" +"Number of items displayed in the main page dashboard before pagination is" +" shown." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:65 +msgid "Admin pages items" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:69 +msgid "" +"Number of items displayed in the admin pages grids before pagination is " +"shown." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:75 +msgid "Icons" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:80 +msgid "Show public repository icon on repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:84 +msgid "Show private repository icon on repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:86 +msgid "Show public/private icons next to repository names." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:92 +msgid "Meta Tagging" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:97 +msgid "Stylify recognised meta tags:" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:111 +msgid "" +"Parses meta tags from the repository description field and turns them " +"into colored tags." +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:5 +msgid "Add user group" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:10 +#: kallithea/templates/admin/user_groups/user_group_edit.html:11 +#: kallithea/templates/admin/user_groups/user_groups.html:10 +#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83 +msgid "User Groups" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:12 +#: kallithea/templates/admin/user_groups/user_groups.html:25 +msgid "Add User Group" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:44 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19 +msgid "Short, optional description for this user group." +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit.html:5 +#, python-format +msgid "%s user group settings" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit.html:33 +msgid "Show Members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1 +#, python-format +msgid "User Group: %s" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32 +#: kallithea/templates/admin/user_groups/user_groups.html:48 +msgid "Members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19 +#: kallithea/templates/data_table/_dt_elements.html:174 +#, python-format +msgid "Confirm to delete this user group: %s" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21 +msgid "Delete this user group" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17 +msgid "No members yet" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40 +msgid "Chosen group members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49 +msgid "Available members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_groups.html:5 +msgid "User Groups Administration" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:5 +msgid "Add user" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:10 +#: kallithea/templates/admin/users/user_edit.html:11 +#: kallithea/templates/admin/users/users.html:10 +#: kallithea/templates/base/base.html:62 +msgid "Users" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:12 +#: kallithea/templates/admin/users/users.html:24 +msgid "Add User" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:50 +msgid "Password confirmation" +msgstr "" + +#: kallithea/templates/admin/users/user_edit.html:5 +#, python-format +msgid "%s user settings" +msgstr "" + +#: kallithea/templates/admin/users/user_edit.html:30 +msgid "Emails" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:1 +#, python-format +msgid "User: %s" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:7 +#: kallithea/templates/admin/users/user_edit_profile.html:42 +msgid "Source of Record" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:9 +#: kallithea/templates/admin/users/users.html:53 +msgid "Last Login" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:10 +msgid "Member of User Groups" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:21 +#: kallithea/templates/data_table/_dt_elements.html:158 +#, python-format +msgid "Confirm to delete this user: %s" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:23 +msgid "Delete this user" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_ips.html:8 +#, python-format +msgid "Inherited from %s" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:8 +msgid "Change avatar at" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:12 +msgid "Missing email, please update this user email address." +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:51 +msgid "Name in Source of Record" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:69 +msgid "New password confirmation" +msgstr "" + +#: kallithea/templates/admin/users/users.html:5 +msgid "Users Administration" +msgstr "" + +#: kallithea/templates/admin/users/users.html:56 +msgid "Auth Type" +msgstr "" + +#: kallithea/templates/base/base.html:18 +#, python-format +msgid "Server instance: %s" +msgstr "" + +#: kallithea/templates/base/base.html:30 +msgid "Support" +msgstr "" + +#: kallithea/templates/base/base.html:90 +msgid "Mercurial repository" +msgstr "" + +#: kallithea/templates/base/base.html:93 +msgid "Git repository" +msgstr "" + +#: kallithea/templates/base/base.html:119 +msgid "Create Fork" +msgstr "" + +#: kallithea/templates/base/base.html:130 +#: kallithea/templates/data_table/_dt_elements.html:13 +#: kallithea/templates/data_table/_dt_elements.html:17 +#: kallithea/templates/summary/summary.html:8 +msgid "Summary" +msgstr "" + +#: kallithea/templates/base/base.html:132 +#: kallithea/templates/base/base.html:134 +#: kallithea/templates/changelog/changelog.html:14 +#: kallithea/templates/data_table/_dt_elements.html:21 +#: kallithea/templates/data_table/_dt_elements.html:25 +msgid "Changelog" +msgstr "" + +#: kallithea/templates/base/base.html:136 +#: kallithea/templates/data_table/_dt_elements.html:29 +#: kallithea/templates/data_table/_dt_elements.html:33 +#: kallithea/templates/files/files.html:11 +msgid "Files" +msgstr "" + +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 +msgid "Options" +msgstr "" + +#: kallithea/templates/base/base.html:152 +#: kallithea/templates/forks/forks_data.html:21 +msgid "Compare Fork" +msgstr "" + +#: kallithea/templates/base/base.html:154 +#: kallithea/templates/bookmarks/bookmarks.html:56 +#: kallithea/templates/bookmarks/bookmarks_data.html:13 +#: kallithea/templates/branches/branches.html:56 +#: kallithea/templates/branches/branches_data.html:13 +#: kallithea/templates/tags/tags.html:56 +#: kallithea/templates/tags/tags_data.html:13 +msgid "Compare" +msgstr "" + +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 +#: kallithea/templates/search/search.html:14 +#: kallithea/templates/search/search.html:54 +msgid "Search" +msgstr "" + +#: kallithea/templates/base/base.html:160 +msgid "Unlock" +msgstr "" + +#: kallithea/templates/base/base.html:162 +msgid "Lock" +msgstr "" + +#: kallithea/templates/base/base.html:170 +msgid "Follow" +msgstr "" + +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + +#: kallithea/templates/base/base.html:174 +#: kallithea/templates/data_table/_dt_elements.html:37 +#: kallithea/templates/data_table/_dt_elements.html:41 +#: kallithea/templates/forks/fork.html:9 +msgid "Fork" +msgstr "" + +#: kallithea/templates/base/base.html:175 +#: kallithea/templates/pullrequests/pullrequest.html:88 +msgid "Create Pull Request" +msgstr "" + +#: kallithea/templates/base/base.html:180 +#, python-format +msgid "Show Pull Requests for %s" +msgstr "" + +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 +msgid "Show recent activity" +msgstr "" + +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 +msgid "Public journal" +msgstr "" + +#: kallithea/templates/base/base.html:317 +msgid "Show public gists" +msgstr "" + +#: kallithea/templates/base/base.html:318 +msgid "Gists" +msgstr "" + +#: kallithea/templates/base/base.html:322 +msgid "All Public Gists" +msgstr "" + +#: kallithea/templates/base/base.html:324 +msgid "My Public Gists" +msgstr "" + +#: kallithea/templates/base/base.html:325 +msgid "My Private Gists" +msgstr "" + +#: kallithea/templates/base/base.html:330 +msgid "Search in repositories" +msgstr "" + +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:6 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:10 +msgid "My Pull Requests" +msgstr "" + +#: kallithea/templates/base/base.html:377 +msgid "Not Logged In" +msgstr "" + +#: kallithea/templates/base/base.html:384 +msgid "Login to Your Account" +msgstr "" + +#: kallithea/templates/base/base.html:407 +msgid "Forgot password ?" +msgstr "" + +#: kallithea/templates/base/base.html:434 +msgid "Log Out" +msgstr "" + +#: kallithea/templates/base/base.html:615 +msgid "Keyboard shortcuts" +msgstr "" + +#: kallithea/templates/base/base.html:624 +msgid "Site-wide shortcuts" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:14 +msgid "Inherit defaults" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:19 +#, python-format +msgid "" +"Select to inherit global settings, IP whitelist and permissions from the " +"%s." +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:28 +msgid "Create repositories" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:33 +msgid "Select this option to allow repository creation for this user" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:40 +msgid "Create user groups" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:45 +msgid "Select this option to allow user group creation for this user" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:52 +msgid "Fork repositories" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:57 +msgid "Select this option to allow repository forking for this user" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:13 +#: kallithea/templates/changelog/changelog.html:42 +msgid "Show" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:22 +msgid "No permissions defined yet" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:30 +#: kallithea/templates/base/perms_summary.html:54 +msgid "Permission" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:32 +#: kallithea/templates/base/perms_summary.html:56 +msgid "Edit Permission" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:90 +msgid "No permission defined" +msgstr "" + +#: kallithea/templates/base/root.html:22 +msgid "Add Another Comment" +msgstr "" + +#: kallithea/templates/base/root.html:23 +#: kallithea/templates/data_table/_dt_elements.html:214 +msgid "Stop following this repository" +msgstr "" + +#: kallithea/templates/base/root.html:24 +msgid "Start following this repository" +msgstr "" + +#: kallithea/templates/base/root.html:25 +msgid "Group" +msgstr "" + +#: kallithea/templates/base/root.html:26 +msgid "members" +msgstr "" + +#: kallithea/templates/base/root.html:27 +msgid "Loading ..." +msgstr "" + +#: kallithea/templates/base/root.html:28 +msgid "loading ..." +msgstr "" + +#: kallithea/templates/base/root.html:29 +msgid "Search truncated" +msgstr "" + +#: kallithea/templates/base/root.html:30 +msgid "No matching files" +msgstr "" + +#: kallithea/templates/base/root.html:31 +msgid "Open New Pull Request from {0}" +msgstr "" + +#: kallithea/templates/base/root.html:32 +msgid "Open New Pull Request for {0} → {1}" +msgstr "" + +#: kallithea/templates/base/root.html:33 +msgid "Show Selected Changesets {0} → {1}" +msgstr "" + +#: kallithea/templates/base/root.html:34 +msgid "Selection Link" +msgstr "" + +#: kallithea/templates/base/root.html:35 +#: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 +msgid "Collapse Diff" +msgstr "" + +#: kallithea/templates/base/root.html:36 +msgid "Expand Diff" +msgstr "" + +#: kallithea/templates/base/root.html:37 +msgid "Failed to revoke permission" +msgstr "" + +#: kallithea/templates/base/root.html:38 +msgid "Confirm to revoke permission for {0}: {1} ?" +msgstr "" + +#: kallithea/templates/base/root.html:39 +msgid "enabled" +msgstr "" + +#: kallithea/templates/base/root.html:40 +msgid "disabled" +msgstr "" + +#: kallithea/templates/base/root.html:42 +msgid "Specify changeset" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:5 +#, python-format +msgid "%s Bookmarks" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:26 +msgid "Compare Bookmarks" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:53 +#: kallithea/templates/bookmarks/bookmarks_data.html:10 +#: kallithea/templates/branches/branches.html:53 +#: kallithea/templates/branches/branches_data.html:10 +#: kallithea/templates/changelog/changelog_summary_data.html:10 +#: kallithea/templates/tags/tags.html:53 +#: kallithea/templates/tags/tags_data.html:10 +msgid "Author" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:54 +#: kallithea/templates/bookmarks/bookmarks_data.html:12 +#: kallithea/templates/branches/branches.html:54 +#: kallithea/templates/branches/branches_data.html:12 +#: kallithea/templates/changelog/changelog_summary_data.html:7 +#: kallithea/templates/files/files_browser.html:32 +#: kallithea/templates/pullrequests/pullrequest.html:62 +#: kallithea/templates/pullrequests/pullrequest.html:78 +#: kallithea/templates/tags/tags.html:54 +#: kallithea/templates/tags/tags_data.html:12 +msgid "Revision" +msgstr "" + +#: kallithea/templates/branches/branches.html:5 +#, python-format +msgid "%s Branches" +msgstr "" + +#: kallithea/templates/branches/branches.html:26 +msgid "Compare Branches" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:6 +#, python-format +msgid "%s Changelog" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:21 +#, python-format +msgid "showing %d out of %d revision" +msgid_plural "showing %d out of %d revisions" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changelog/changelog.html:49 +msgid "Clear selection" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:55 +msgid "Go to tip of repository" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:60 +#: kallithea/templates/forks/forks_data.html:19 +#, python-format +msgid "Compare fork with %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:62 +#, python-format +msgid "Compare fork with parent repository (%s)" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:66 +#: kallithea/templates/files/files.html:29 +msgid "Branch filter:" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:92 +#: kallithea/templates/changelog/changelog_summary_data.html:20 +#, python-format +msgid "" +"Changeset status: %s by %s\n" +"Click to open associated pull request %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:96 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, python-format +msgid "Changeset status: %s by %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:116 +#: kallithea/templates/compare/compare_cs.html:63 +msgid "Expand commit message" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:125 +#: kallithea/templates/compare/compare_cs.html:30 +msgid "Changeset has comments" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 +#: kallithea/templates/changeset/changeset.html:94 +#: kallithea/templates/changeset/changeset_range.html:92 +#, python-format +msgid "Bookmark %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 +#: kallithea/templates/changeset/changeset.html:101 +#: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 +#, python-format +msgid "Tag %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 +#: kallithea/templates/changeset/changeset.html:106 +#: kallithea/templates/changeset/changeset_range.html:102 +#, python-format +msgid "Branch %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:311 +msgid "There are no changes yet" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:4 +#: kallithea/templates/changeset/changeset.html:77 +msgid "Removed" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:5 +#: kallithea/templates/changeset/changeset.html:78 +msgid "Changed" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:6 +#: kallithea/templates/changeset/changeset.html:79 +#: kallithea/templates/changeset/diff_block.html:47 +msgid "Added" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:8 +#: kallithea/templates/changelog/changelog_details.html:9 +#: kallithea/templates/changelog/changelog_details.html:10 +#: kallithea/templates/changeset/changeset.html:81 +#: kallithea/templates/changeset/changeset.html:82 +#: kallithea/templates/changeset/changeset.html:83 +#, python-format +msgid "Affected %s files" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:8 +#: kallithea/templates/files/files_add.html:60 +#: kallithea/templates/files/files_delete.html:39 +#: kallithea/templates/files/files_edit.html:63 +msgid "Commit Message" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:9 +#: kallithea/templates/pullrequests/pullrequest_data.html:17 +msgid "Age" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:11 +msgid "Refs" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:84 +msgid "Add or upload files directly via Kallithea" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:87 +#: kallithea/templates/files/files_add.html:21 +#: kallithea/templates/files/files_ypjax.html:9 +msgid "Add New File" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:93 +msgid "Push new repository" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:101 +msgid "Existing repository?" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:8 +#, python-format +msgid "%s Changeset" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:36 +msgid "Parent rev." +msgstr "" + +#: kallithea/templates/changeset/changeset.html:42 +msgid "Child rev." +msgstr "" + +#: kallithea/templates/changeset/changeset.html:50 +#: kallithea/templates/changeset/changeset_file_comment.html:39 +#: kallithea/templates/changeset/changeset_range.html:48 +msgid "Changeset status" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:54 +#: kallithea/templates/changeset/diff_block.html:72 +#: kallithea/templates/files/diff_2way.html:49 +msgid "Raw diff" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:57 +msgid "Patch diff" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:60 +#: kallithea/templates/changeset/diff_block.html:75 +#: kallithea/templates/files/diff_2way.html:52 +msgid "Download diff" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:89 +#: kallithea/templates/changeset/changeset_range.html:88 +msgid "Merge" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:123 +msgid "Grafted from:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:129 +msgid "Transplanted from:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:135 +msgid "Replaced by:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:149 +msgid "Preceded by:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:166 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 +#, python-format +msgid "%s file changed" +msgid_plural "%s files changed" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset.html:168 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 +#, python-format +msgid "%s file changed with %s insertions and %s deletions" +msgid_plural "%s files changed with %s insertions and %s deletions" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset.html:182 +#: kallithea/templates/changeset/changeset.html:195 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 +msgid "Show full diff anyway" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 +msgid "No revisions" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:21 +msgid "on pull request" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:22 +msgid "No title" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:24 +msgid "on this changeset" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:31 +msgid "Delete comment?" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:39 +msgid "Status change" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:59 +msgid "Commenting on line." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:60 +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 +msgid "Submitting ..." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:104 +msgid "Comment" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:112 +msgid "You need to be logged in to comment." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:112 +msgid "Login now" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:116 +msgid "Hide" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:128 +#, python-format +msgid "%d comment" +msgid_plural "%d comments" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset_file_comment.html:129 +#, python-format +msgid "%d inline" +msgid_plural "%d inline" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset_file_comment.html:130 +#, python-format +msgid "%d general" +msgid_plural "%d general" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset_range.html:5 +#, python-format +msgid "%s Changesets" +msgstr "" + +#: kallithea/templates/changeset/changeset_range.html:56 +msgid "Files affected" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:66 +#: kallithea/templates/files/diff_2way.html:43 +msgid "Show full diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:69 +#: kallithea/templates/files/diff_2way.html:46 +msgid "Show full side-by-side diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:83 +msgid "Show inline comments" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:4 +msgid "No changesets" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:8 +msgid "Ancestor" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:44 +msgid "First (oldest) changeset in this list" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:46 +msgid "Last (most recent) changeset in this list" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:48 +msgid "Position in this list of changesets" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:85 +msgid "Show merge diff" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 +msgid "Common ancestor" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 +#, python-format +msgid "%s changesets" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:109 +msgid "behind" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:6 +#: kallithea/templates/compare/compare_diff.html:8 +#, python-format +msgid "%s Compare" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:13 +#: kallithea/templates/compare/compare_diff.html:41 +msgid "Compare Revisions" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:39 +msgid "Swap" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:48 +msgid "Compare revisions, branches, bookmarks, or tags." +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 +#, python-format +msgid "Showing %s commit" +msgid_plural "Showing %s commits" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +msgid "Show full diff" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:74 +msgid "Public repository" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:84 +msgid "Repository creation in progress..." +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:98 +msgid "No changesets yet" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:105 +#: kallithea/templates/data_table/_dt_elements.html:107 +#, python-format +msgid "Subscribe to %s rss feed" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:113 +#: kallithea/templates/data_table/_dt_elements.html:115 +#, python-format +msgid "Subscribe to %s atom feed" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:139 +msgid "Creating" +msgstr "" + +#: kallithea/templates/email_templates/changeset_comment.html:5 +#, python-format +msgid "Comment from %s on %s changeset %s mentioned you" +msgstr "" + +#: kallithea/templates/email_templates/changeset_comment.html:7 +#, python-format +msgid "Comment from %s on %s changeset %s" +msgstr "" + +#: kallithea/templates/email_templates/changeset_comment.html:12 +msgid "The changeset status was changed to" +msgstr "" + +#: kallithea/templates/email_templates/main.html:6 +msgid "This is an automatic notification. Don't reply to this mail." +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:4 +#, python-format +msgid "Hello %s" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:6 +msgid "We have received a request to reset the password for your account." +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 +msgid "" +"Should you not be able to use the link above, please type the following " +"code into the password reset form" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:16 +msgid "" +"If it weren't you who requested the password reset, just disregard this " +"message." +msgstr "" + +#: kallithea/templates/email_templates/pull_request.html:5 +#, python-format +msgid "%s mentioned you on %s pull request \"%s\"" +msgstr "" + +#: kallithea/templates/email_templates/pull_request.html:7 +#, python-format +msgid "%s requested your review of %s pull request \"%s\"" +msgstr "" + +#: kallithea/templates/email_templates/pull_request_comment.html:4 +#, python-format +msgid "Comment from %s on %s pull request \"%s\"" +msgstr "" + +#: kallithea/templates/email_templates/pull_request_comment.html:9 +msgid "The comment closed the pull request with status" +msgstr "" + +#: kallithea/templates/email_templates/pull_request_comment.html:11 +msgid "The comment was made with status" +msgstr "" + +#: kallithea/templates/email_templates/registration.html:6 +msgid "View this user here" +msgstr "" + +#: kallithea/templates/files/diff_2way.html:15 +#, python-format +msgid "%s File side-by-side diff" +msgstr "" + +#: kallithea/templates/files/diff_2way.html:19 +#: kallithea/templates/files/file_diff.html:8 +msgid "File diff" +msgstr "" + +#: kallithea/templates/files/file_diff.html:4 +#, python-format +msgid "%s File Diff" +msgstr "" + +#: kallithea/templates/files/files.html:4 +#: kallithea/templates/files/files.html:80 +#, python-format +msgid "%s Files" +msgstr "" + +#: kallithea/templates/files/files_add.html:4 +#, python-format +msgid "%s Files Add" +msgstr "" + +#: kallithea/templates/files/files_add.html:40 +#: kallithea/templates/files/files_edit.html:38 +#: kallithea/templates/files/files_ypjax.html:3 +msgid "Location" +msgstr "" + +#: kallithea/templates/files/files_add.html:42 +msgid "Enter filename..." +msgstr "" + +#: kallithea/templates/files/files_add.html:44 +#: kallithea/templates/files/files_add.html:48 +msgid "or" +msgstr "" + +#: kallithea/templates/files/files_add.html:44 +msgid "Upload File" +msgstr "" + +#: kallithea/templates/files/files_add.html:48 +msgid "Create New File" +msgstr "" + +#: kallithea/templates/files/files_add.html:53 +#, fuzzy +msgid "New file type" +msgstr "Άγνωστος τύπος αρχειοθέτησης" + +#: kallithea/templates/files/files_add.html:64 +#: kallithea/templates/files/files_delete.html:43 +#: kallithea/templates/files/files_edit.html:67 +msgid "Commit Changes" +msgstr "" + +#: kallithea/templates/files/files_browser.html:33 +msgid "Previous revision" +msgstr "" + +#: kallithea/templates/files/files_browser.html:35 +msgid "Next revision" +msgstr "" + +#: kallithea/templates/files/files_browser.html:41 +msgid "Follow current branch" +msgstr "" + +#: kallithea/templates/files/files_browser.html:44 +msgid "Search File List" +msgstr "" + +#: kallithea/templates/files/files_browser.html:48 +msgid "Loading file list..." +msgstr "" + +#: kallithea/templates/files/files_browser.html:61 +msgid "Size" +msgstr "" + +#: kallithea/templates/files/files_browser.html:62 +msgid "Last Revision" +msgstr "" + +#: kallithea/templates/files/files_browser.html:63 +msgid "Last Modified" +msgstr "" + +#: kallithea/templates/files/files_browser.html:64 +msgid "Last Committer" +msgstr "" + +#: kallithea/templates/files/files_delete.html:4 +#, python-format +msgid "%s Files Delete" +msgstr "" + +#: kallithea/templates/files/files_delete.html:12 +#: kallithea/templates/files/files_delete.html:31 +msgid "Delete file" +msgstr "" + +#: kallithea/templates/files/files_edit.html:4 +#, python-format +msgid "%s File Edit" +msgstr "" + +#: kallithea/templates/files/files_edit.html:21 +msgid "Edit file" +msgstr "" + +#: kallithea/templates/files/files_edit.html:48 +#: kallithea/templates/files/files_source.html:32 +msgid "Show Annotation" +msgstr "" + +#: kallithea/templates/files/files_edit.html:50 +#: kallithea/templates/files/files_source.html:35 +msgid "Download as Raw" +msgstr "" + +#: kallithea/templates/files/files_edit.html:53 +msgid "Source" +msgstr "" + +#: kallithea/templates/files/files_edit.html:58 +msgid "Editing file" +msgstr "" + +#: kallithea/templates/files/files_history_box.html:2 +#, python-format +msgid "%s author" +msgid_plural "%s authors" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/files/files_source.html:7 +msgid "Diff to Revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:8 +msgid "Show at Revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:10 +msgid "Show Full History" +msgstr "" + +#: kallithea/templates/files/files_source.html:11 +msgid "Show Authors" +msgstr "" + +#: kallithea/templates/files/files_source.html:30 +msgid "Show Source" +msgstr "" + +#: kallithea/templates/files/files_source.html:38 +#, python-format +msgid "Edit on Branch:%s" +msgstr "" + +#: kallithea/templates/files/files_source.html:41 +msgid "Editing binary files not allowed" +msgstr "" + +#: kallithea/templates/files/files_source.html:44 +msgid "Editing files allowed only when on branch head revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:45 +msgid "Deleting files allowed only when on branch head revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:63 +#, python-format +msgid "Binary file (%s)" +msgstr "" + +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "" + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." +msgstr "" + +#: kallithea/templates/files/files_ypjax.html:5 +msgid "annotation" +msgstr "" + +#: kallithea/templates/files/files_ypjax.html:23 +msgid "Go Back" +msgstr "" + +#: kallithea/templates/files/files_ypjax.html:24 +msgid "No files at given path" +msgstr "" + +#: kallithea/templates/followers/followers.html:5 +#, python-format +msgid "%s Followers" +msgstr "" + +#: kallithea/templates/followers/followers.html:9 +#: kallithea/templates/summary/summary.html:142 +#: kallithea/templates/summary/summary.html:143 +msgid "Followers" +msgstr "" + +#: kallithea/templates/followers/followers_data.html:12 +msgid "Started following -" +msgstr "" + +#: kallithea/templates/forks/fork.html:5 +#, python-format +msgid "Fork repository %s" +msgstr "" + +#: kallithea/templates/forks/fork.html:27 +msgid "Fork name" +msgstr "" + +#: kallithea/templates/forks/fork.html:62 +msgid "Default revision for files page, downloads, whoosh, and readme." +msgstr "" + +#: kallithea/templates/forks/fork.html:68 +msgid "Private" +msgstr "" + +#: kallithea/templates/forks/fork.html:77 +msgid "Copy permissions" +msgstr "" + +#: kallithea/templates/forks/fork.html:81 +msgid "Copy permissions from forked repository" +msgstr "" + +#: kallithea/templates/forks/fork.html:87 +msgid "Update after clone" +msgstr "" + +#: kallithea/templates/forks/fork.html:91 +msgid "Checkout source after making a clone" +msgstr "" + +#: kallithea/templates/forks/fork.html:96 +msgid "Fork this Repository" +msgstr "" + +#: kallithea/templates/forks/forks.html:5 +#, python-format +msgid "%s Forks" +msgstr "" + +#: kallithea/templates/forks/forks.html:9 +#: kallithea/templates/summary/summary.html:148 +#: kallithea/templates/summary/summary.html:149 +msgid "Forks" +msgstr "" + +#: kallithea/templates/forks/forks_data.html:17 +msgid "Forked" +msgstr "" + +#: kallithea/templates/forks/forks_data.html:30 +msgid "There are no forks yet" +msgstr "" + +#: kallithea/templates/journal/journal.html:21 +msgid "ATOM journal feed" +msgstr "" + +#: kallithea/templates/journal/journal.html:22 +msgid "RSS journal feed" +msgstr "" + +#: kallithea/templates/journal/journal.html:56 +msgid "My Repositories" +msgstr "" + +#: kallithea/templates/journal/journal_data.html:43 +msgid "No entries yet" +msgstr "" + +#: kallithea/templates/journal/public_journal.html:13 +msgid "ATOM public journal feed" +msgstr "" + +#: kallithea/templates/journal/public_journal.html:14 +msgid "RSS public journal feed" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:4 +#: kallithea/templates/pullrequests/pullrequest.html:8 +msgid "New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:31 +#: kallithea/templates/pullrequests/pullrequest_data.html:15 +#: kallithea/templates/pullrequests/pullrequest_show.html:29 +#: kallithea/templates/pullrequests/pullrequest_show.html:54 +msgid "Title" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:34 +msgid "Summarize the changes - or leave empty" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:43 +#: kallithea/templates/pullrequests/pullrequest_show.html:66 +msgid "Write a short description on this pull request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:49 +msgid "Changeset flow" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:56 +msgid "Origin repository" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:72 +msgid "Destination repository" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:6 +msgid "No entries" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:14 +msgid "Vote" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:18 +msgid "From" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:19 +msgid "To" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:28 +#, python-format +msgid "You voted: %s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:30 +msgid "You didn't vote" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:35 +msgid "(no title)" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:37 +#: kallithea/templates/pullrequests/pullrequest_show.html:31 +#: kallithea/templates/pullrequests/pullrequest_show.html:83 +msgid "Closed" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:67 +msgid "Delete Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:68 +msgid "Confirm to delete this pull request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:70 +#, python-format +msgid "Confirm again to delete this pull request with %s comments" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:6 +#, python-format +msgid "%s Pull Request %s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:10 +#, python-format +msgid "Pull request %s from %s#%s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:57 +msgid "Summarize the changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:74 +msgid "Reviewer voting result" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:80 +#: kallithea/templates/pullrequests/pullrequest_show.html:81 +msgid "Pull request status calculated from votes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:93 +msgid "Still not reviewed by" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:97 +#, python-format +msgid "%d reviewer" +msgid_plural "%d reviewers" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:99 +msgid "Pull request was reviewed by all reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:101 +msgid "There are no reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:107 +msgid "Origin" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:113 +msgid "on" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:120 +msgid "Target" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:124 +msgid "" +"This is just a range of changesets and doesn't have a target or a real " +"merge ancestor." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:133 +msgid "Pull changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:173 +msgid "Update" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:191 +msgid "Current revision - no change" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 +msgid "Pull Request Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:249 +msgid "Remove reviewer" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +msgid "Potential Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 +msgid "Click to add the repository owner as reviewer:" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:295 +msgid "Save Changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#, fuzzy +msgid "Save Updates as New Pull Request" +msgstr "Ένα νέο αίτημα έλξης (pull request) δημιουργήθηκε επιτυχώς" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 +msgid "Cancel Changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:307 +msgid "Pull Request Content" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:6 +#, python-format +msgid "%s Pull Requests" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:11 +#, python-format +msgid "Pull Requests from '%s'" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:13 +#, python-format +msgid "Pull Requests to '%s'" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:32 +msgid "Open New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:37 +#, python-format +msgid "Show Pull Requests to %s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:39 +#, python-format +msgid "Show Pull Requests from '%s'" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:49 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:28 +msgid "Hide closed pull requests (only show open pull requests)" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:51 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:30 +msgid "Show closed pull requests (in addition to open pull requests)" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_my.html:35 +msgid "Pull Requests Created by Me" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_my.html:38 +msgid "Pull Requests I Participate In" +msgstr "" + +#: kallithea/templates/search/search.html:6 +#, python-format +msgid "%s Search" +msgstr "" + +#: kallithea/templates/search/search.html:8 +#: kallithea/templates/search/search.html:16 +msgid "Search in All Repositories" +msgstr "" + +#: kallithea/templates/search/search.html:50 +msgid "Search term" +msgstr "" + +#: kallithea/templates/search/search.html:62 +msgid "Search in" +msgstr "" + +#: kallithea/templates/search/search.html:65 +msgid "File contents" +msgstr "" + +#: kallithea/templates/search/search.html:66 +msgid "Commit messages" +msgstr "" + +#: kallithea/templates/search/search.html:67 +msgid "File names" +msgstr "" + +#: kallithea/templates/search/search_commit.html:35 +#: kallithea/templates/search/search_content.html:21 +#: kallithea/templates/search/search_path.html:15 +msgid "Permission denied" +msgstr "" + +#: kallithea/templates/summary/statistics.html:4 +#, python-format +msgid "%s Statistics" +msgstr "" + +#: kallithea/templates/summary/statistics.html:16 +#: kallithea/templates/summary/summary.html:39 +#, python-format +msgid "%s ATOM feed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:17 +#: kallithea/templates/summary/summary.html:40 +#, python-format +msgid "%s RSS feed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:36 +#: kallithea/templates/summary/summary.html:100 +#: kallithea/templates/summary/summary.html:116 +msgid "Enable" +msgstr "" + +#: kallithea/templates/summary/statistics.html:39 +msgid "Stats gathered: " +msgstr "" + +#: kallithea/templates/summary/statistics.html:89 +#: kallithea/templates/summary/summary.html:349 +msgid "files" +msgstr "" + +#: kallithea/templates/summary/statistics.html:113 +#: kallithea/templates/summary/summary.html:373 +msgid "Show more" +msgstr "" + +#: kallithea/templates/summary/statistics.html:390 +msgid "commits" +msgstr "" + +#: kallithea/templates/summary/statistics.html:391 +msgid "files added" +msgstr "" + +#: kallithea/templates/summary/statistics.html:392 +msgid "files changed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:393 +msgid "files removed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:395 +msgid "commit" +msgstr "" + +#: kallithea/templates/summary/statistics.html:396 +msgid "file added" +msgstr "" + +#: kallithea/templates/summary/statistics.html:397 +msgid "file changed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:398 +msgid "file removed" +msgstr "" + +#: kallithea/templates/summary/summary.html:4 +#, python-format +msgid "%s Summary" +msgstr "" + +#: kallithea/templates/summary/summary.html:13 +#, python-format +msgid "Repository locked by %s" +msgstr "" + +#: kallithea/templates/summary/summary.html:15 +msgid "Repository unlocked" +msgstr "" + +#: kallithea/templates/summary/summary.html:22 +msgid "Fork of" +msgstr "" + +#: kallithea/templates/summary/summary.html:29 +msgid "Clone from" +msgstr "" + +#: kallithea/templates/summary/summary.html:72 +msgid "Clone URL" +msgstr "" + +#: kallithea/templates/summary/summary.html:78 +msgid "Show by Name" +msgstr "" + +#: kallithea/templates/summary/summary.html:79 +msgid "Show by ID" +msgstr "" + +#: kallithea/templates/summary/summary.html:92 +msgid "Trending files" +msgstr "" + +#: kallithea/templates/summary/summary.html:108 +msgid "Download" +msgstr "" + +#: kallithea/templates/summary/summary.html:112 +msgid "There are no downloads yet" +msgstr "" + +#: kallithea/templates/summary/summary.html:114 +msgid "Downloads are disabled for this repository" +msgstr "" + +#: kallithea/templates/summary/summary.html:120 +msgid "Download as zip" +msgstr "" + +#: kallithea/templates/summary/summary.html:125 +msgid "Check this to download archive with subrepos" +msgstr "" + +#: kallithea/templates/summary/summary.html:125 +msgid "With subrepos" +msgstr "" + +#: kallithea/templates/summary/summary.html:156 +msgid "Repository Size" +msgstr "" + +#: kallithea/templates/summary/summary.html:163 +#: kallithea/templates/summary/summary.html:165 +msgid "Feed" +msgstr "" + +#: kallithea/templates/summary/summary.html:186 +msgid "Latest Changes" +msgstr "" + +#: kallithea/templates/summary/summary.html:188 +msgid "Quick Start" +msgstr "" + +#: kallithea/templates/summary/summary.html:202 +#, python-format +msgid "Readme file from revision %s:%s" +msgstr "" + +#: kallithea/templates/summary/summary.html:293 +#, python-format +msgid "Download %s as %s" +msgstr "" + +#: kallithea/templates/tags/tags.html:5 +#, python-format +msgid "%s Tags" +msgstr "" + +#: kallithea/templates/tags/tags.html:26 +msgid "Compare Tags" +msgstr "" + +#~ msgid "" +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "Αυτό το αίτημα έλξης μπορεί να ενημερωθεί με αλλαγές στο %s:" + +#~ msgid "Non-admins can can fork repositories" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "" + +#~ msgid "Commenting on line {1}." +#~ msgstr "" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "" + +#~ msgid "Preview" +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user." +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "File is too big to display" +#~ msgstr "" + +#~ msgid "Save as New Pull Request" +#~ msgstr "" + +#~ msgid "Pull Requests from %s'" +#~ msgstr "" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/es/LC_MESSAGES/kallithea.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/i18n/es/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,5814 @@ +# Translations template for Kallithea. +# Copyright (C) 2016 Various authors, licensing as GPLv3 +# This file is distributed under the same license as the Kallithea project. +# FIRST AUTHOR , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: Kallithea 0.3\n" +"Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" +"POT-Creation-Date: 2016-02-22 19:35+0100\n" +"PO-Revision-Date: 2016-03-08 09:09+0000\n" +"Last-Translator: Oscar Curero \n" +"Language-Team: Spanish " +"\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 2.5-dev\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:237 kallithea/lib/base.py:515 +msgid "There are no changesets yet" +msgstr "Aún no hay cambios" + +#: kallithea/controllers/changelog.py:164 +#: kallithea/controllers/admin/permissions.py:61 +#: kallithea/controllers/admin/permissions.py:65 +#: kallithea/controllers/admin/permissions.py:69 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:8 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7 +#: kallithea/templates/base/perms_summary.html:14 +msgid "None" +msgstr "Ninguno" + +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:197 +msgid "(closed)" +msgstr "(cerrado)" + +#: kallithea/controllers/changeset.py:88 +msgid "Show whitespace" +msgstr "Mostrar espacios en blanco" + +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 +#: kallithea/templates/files/diff_2way.html:55 +msgid "Ignore whitespace" +msgstr "Ignorar espacios en blanco" + +#: kallithea/controllers/changeset.py:168 +#, python-format +msgid "Increase diff context to %(num)s lines" +msgstr "Aumentar el contexto del diff a %(num)s lineas" + +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:96 +#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:743 +#, fuzzy +msgid "Such revision does not exist for this repository" +msgstr "La revisión no existe en este repositorio" + +#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 +msgid "Select changeset" +msgstr "Seleccionar cambios" + +#: kallithea/controllers/compare.py:261 +msgid "Cannot compare repositories without using common ancestor" +msgstr "No se pueden comparar repositorios sin usar un ancestro común" + +#: kallithea/controllers/error.py:71 +msgid "No response" +msgstr "No hay respuesta" + +#: kallithea/controllers/error.py:72 +msgid "Unknown error" +msgstr "Error desconocido" + +#: kallithea/controllers/error.py:100 +msgid "The request could not be understood by the server due to malformed syntax." +msgstr "" +"La petición no ha podido ser atendida por el servidor debido un error de " +"sintaxis." + +#: kallithea/controllers/error.py:103 +msgid "Unauthorized access to resource" +msgstr "Acceso no autorizado al recurso" + +#: kallithea/controllers/error.py:105 +msgid "You don't have permission to view this page" +msgstr "No tiene permiso para ver esta página" + +#: kallithea/controllers/error.py:107 +msgid "The resource could not be found" +msgstr "No se ha encontrado el recurso" + +#: kallithea/controllers/error.py:109 +msgid "The server encountered an unexpected condition which prevented it from fulfilling the request." +msgstr "" +"La petición no se ha podido completar debido a que el servidor encontró un " +"problema inesperado." + +#: kallithea/controllers/feed.py:55 +#, fuzzy, python-format +msgid "Changes on %s repository" +msgstr "Cambios en %s repositorio" + +#: kallithea/controllers/feed.py:56 +#, python-format +msgid "%s %s feed" +msgstr "%s%s canal" + +#: kallithea/controllers/feed.py:87 +#: kallithea/templates/changeset/changeset.html:182 +#: kallithea/templates/changeset/changeset.html:195 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 +msgid "Changeset was too big and was cut off..." +msgstr "El cambio era demasiado grande y se redució..." + +#: kallithea/controllers/feed.py:91 +#, python-format +msgid "%s committed on %s" +msgstr "%s anotó en %s" + +#: kallithea/controllers/files.py:91 +msgid "Click here to add new file" +msgstr "Haga clic aquí para añadir un archivo nuevo" + +#: kallithea/controllers/files.py:92 +#, python-format +msgid "There are no files yet. %s" +msgstr "Aún no hay archivos. %s" + +#: kallithea/controllers/files.py:194 +#, python-format +msgid "%s at %s" +msgstr "%s en %s" + +#: kallithea/controllers/files.py:306 kallithea/controllers/files.py:366 +#: kallithea/controllers/files.py:433 +#, python-format +msgid "This repository has been locked by %s on %s" +msgstr "Este repositorio ha sido bloqueado por %s en %s" + +#: kallithea/controllers/files.py:318 +msgid "You can only delete files with revision being a valid branch" +msgstr "Sólo puede borrar archivos si la revisión pertenece a una rama válida" + +#: kallithea/controllers/files.py:329 +#, python-format +msgid "Deleted file %s via Kallithea" +msgstr "Archivo %s eliminado mediante Kallithea" + +#: kallithea/controllers/files.py:351 +#, python-format +msgid "Successfully deleted file %s" +msgstr "El archivo %s se eliminó correctamente" + +#: kallithea/controllers/files.py:355 kallithea/controllers/files.py:421 +#: kallithea/controllers/files.py:502 +msgid "Error occurred during commit" +msgstr "Ocurrió un error al anotar los cambios" + +#: kallithea/controllers/files.py:378 +msgid "You can only edit files with revision being a valid branch" +msgstr "Sólo puede editar archivos si la revisión pertenece a una rama válida" + +#: kallithea/controllers/files.py:392 +#, python-format +msgid "Edited file %s via Kallithea" +msgstr "Archivo %s editado mediante Kallithea" + +#: kallithea/controllers/files.py:408 +msgid "No changes" +msgstr "No hay cambios" + +#: kallithea/controllers/files.py:417 kallithea/controllers/files.py:491 +#, python-format +msgid "Successfully committed to %s" +msgstr "Anotado correctamente a %s" + +#: kallithea/controllers/files.py:444 +msgid "Added file via Kallithea" +msgstr "Archivo añadido mediante Kallithea" + +#: kallithea/controllers/files.py:465 +msgid "No content" +msgstr "Sin contenido" + +#: kallithea/controllers/files.py:469 +msgid "No filename" +msgstr "Sin nombre de archivo" + +#: kallithea/controllers/files.py:494 +#, fuzzy +msgid "Location must be relative path and must not contain .. in path" +msgstr "La ruta debe ser relativa y no debe contener .. en la ruta" + +#: kallithea/controllers/files.py:527 +msgid "Downloads disabled" +msgstr "Descargas deshabilitadas" + +#: kallithea/controllers/files.py:538 +#, python-format +msgid "Unknown revision %s" +msgstr "Revisión desconocida %s" + +#: kallithea/controllers/files.py:540 +msgid "Empty repository" +msgstr "Repositorio vacío" + +#: kallithea/controllers/files.py:542 +msgid "Unknown archive type" +msgstr "Tipo de archivo desconocido" + +#: kallithea/controllers/files.py:772 +#: kallithea/templates/changeset/changeset_range.html:9 +#: kallithea/templates/email_templates/pull_request.html:15 +#: kallithea/templates/pullrequests/pullrequest.html:97 +msgid "Changesets" +msgstr "Cambios" + +#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/templates/branches/branches.html:10 +msgid "Branches" +msgstr "Ramas" + +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/templates/tags/tags.html:10 +msgid "Tags" +msgstr "Etiquetas" + +#: kallithea/controllers/forks.py:186 +#, python-format +msgid "An error occurred during repository forking %s" +msgstr "Ocurrió un error mientras se bifurcaba el repositorio %s" + +#: kallithea/controllers/home.py:84 +msgid "Groups" +msgstr "Grupos" + +#: kallithea/controllers/home.py:89 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 +#: kallithea/templates/admin/repos/repo_add.html:12 +#: kallithea/templates/admin/repos/repo_add.html:16 +#: kallithea/templates/admin/repos/repos.html:9 +#: kallithea/templates/admin/users/user_edit_advanced.html:6 +#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 +#: kallithea/templates/base/base.html:124 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 +msgid "Repositories" +msgstr "Repositorios" + +#: kallithea/controllers/home.py:130 +#: kallithea/templates/files/files_add.html:32 +#: kallithea/templates/files/files_delete.html:23 +#: kallithea/templates/files/files_edit.html:32 +msgid "Branch" +msgstr "Rama" + +#: kallithea/controllers/home.py:136 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "Ramas cerradas" + +#: kallithea/controllers/home.py:142 +msgid "Tag" +msgstr "Etiqueta" + +#: kallithea/controllers/home.py:148 +msgid "Bookmark" +msgstr "Marcador" + +#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153 +#: kallithea/templates/journal/public_journal.html:4 +#: kallithea/templates/journal/public_journal.html:21 +msgid "Public Journal" +msgstr "Registro público" + +#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 +#: kallithea/templates/base/base.html:306 +#: kallithea/templates/journal/journal.html:4 +#: kallithea/templates/journal/journal.html:12 +msgid "Journal" +msgstr "Registro" + +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 +msgid "Bad captcha" +msgstr "CAPTCHA erróneo" + +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "El registro en %s se ha efectuado correctamente" + +#: kallithea/controllers/login.py:195 +msgid "A password reset confirmation code has been sent" +msgstr "Se ha enviado una confirmación de restauración de contraseña" + +#: kallithea/controllers/login.py:244 +msgid "Invalid password reset token" +msgstr "Señal de restauración de contraseña inválida" + +#: kallithea/controllers/login.py:249 +#: kallithea/controllers/admin/my_account.py:167 +msgid "Successfully updated password" +msgstr "Contraseña actualizada correctamente" + +#: kallithea/controllers/pullrequests.py:123 +#, python-format +msgid "%s (closed)" +msgstr "%s (cerrado)" + +#: kallithea/controllers/pullrequests.py:151 +#: kallithea/templates/changeset/changeset.html:12 +#: kallithea/templates/email_templates/changeset_comment.html:17 +msgid "Changeset" +msgstr "Cambio" + +#: kallithea/controllers/pullrequests.py:172 +msgid "Special" +msgstr "Especial" + +#: kallithea/controllers/pullrequests.py:173 +#, fuzzy +msgid "Peer branches" +msgstr "Ramas de los pares" + +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:826 +#: kallithea/templates/switch_to_list.html:38 +#: kallithea/templates/bookmarks/bookmarks.html:10 +msgid "Bookmarks" +msgstr "Marcadores" + +#: kallithea/controllers/pullrequests.py:309 +#, python-format +msgid "Error creating pull request: %s" +msgstr "Error al crear la petición de pull: %s" + +#: kallithea/controllers/pullrequests.py:355 +#: kallithea/controllers/pullrequests.py:502 +msgid "No description" +msgstr "No hay descripción" + +#: kallithea/controllers/pullrequests.py:362 +msgid "Successfully opened new pull request" +msgstr "La petición de pull se ha creado correctamente" + +#: kallithea/controllers/pullrequests.py:365 +#: kallithea/controllers/pullrequests.py:452 +#: kallithea/controllers/pullrequests.py:509 +#, python-format +msgid "Invalid reviewer \"%s\" specified" +msgstr "El validador \"%s\" no es correcto" + +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +msgid "Error occurred while creating pull request" +msgstr "Ocurrió un error al crear la petición de pull" + +#: kallithea/controllers/pullrequests.py:400 +msgid "Missing changesets since the previous pull request:" +msgstr "Cambios que faltan desde la ultima petición de pull:" + +#: kallithea/controllers/pullrequests.py:407 +#, fuzzy, python-format +msgid "New changesets on %s %s since the previous pull request:" +msgstr "Cambios nuevos en %s %s desde la ultima petición pull:" + +#: kallithea/controllers/pullrequests.py:414 +msgid "Ancestor didn't change - show diff since previous version:" +msgstr "" +"El ascendente no ha cambiado - ver diferencias desde la versión anterior:" + +#: kallithea/controllers/pullrequests.py:421 +#, python-format +msgid "This pull request is based on another %s revision and there is no simple diff." +msgstr "" +"La petición de pull está basada en otra %s revisión y no hay un diff simple." + +#: kallithea/controllers/pullrequests.py:423 +#, python-format +msgid "No changes found on %s %s since previous version." +msgstr "No se encontró ningún cambio en %s %s desde la versión anterior." + +#: kallithea/controllers/pullrequests.py:461 +#, python-format +msgid "Closed, replaced by %s ." +msgstr "Cerrado, reemplazado por %s." + +#: kallithea/controllers/pullrequests.py:469 +msgid "Pull request update created" +msgstr "Actualización de la petición pull creada" + +#: kallithea/controllers/pullrequests.py:513 +msgid "Pull request updated" +msgstr "Petición pull actualizada" + +#: kallithea/controllers/pullrequests.py:528 +msgid "Successfully deleted pull request" +msgstr "Petición pull eliminada correctamente" + +#: kallithea/controllers/pullrequests.py:594 +#, python-format +msgid "This pull request has already been merged to %s." +msgstr "La petición pull ya ha sido incluida a %s." + +#: kallithea/controllers/pullrequests.py:596 +msgid "This pull request has been closed and can not be updated." +msgstr "La petición pull esta cerrada y no se puede actualizar." + +#: kallithea/controllers/pullrequests.py:614 +#, python-format +msgid "The following changes are available on %s:" +msgstr "Los siguientes cambios están disponibles en %s:" + +#: kallithea/controllers/pullrequests.py:618 +msgid "No changesets found for updating this pull request." +msgstr "No se encontraron cambios para actualizar la petición pull." + +#: kallithea/controllers/pullrequests.py:626 +#, fuzzy, python-format +msgid "Note: Branch %s has another head: %s." +msgstr "Nota: la rama %s tiene otro head: %s." + +#: kallithea/controllers/pullrequests.py:632 +msgid "Git pull requests don't support updates yet." +msgstr "La peticiones pull de Git aún no soportan actualizaciones." + +#: kallithea/controllers/pullrequests.py:724 +msgid "No permission to change pull request status" +msgstr "No tene permiso para cambiar el estado de la petición pull" + +#: kallithea/controllers/pullrequests.py:735 +#, python-format +msgid "Successfully deleted pull request %s" +msgstr "Petición de pull %s eliminada correctamente" + +#: kallithea/controllers/pullrequests.py:745 +msgid "Closing." +msgstr "Cerrado." + +#: kallithea/controllers/search.py:135 +msgid "Invalid search query. Try quoting it." +msgstr "Consulta de búsqueda inválida. Inténtelo entre comillas." + +#: kallithea/controllers/search.py:140 +msgid "There is no index to search in. Please run whoosh indexer" +msgstr "" +"No hay ningún indice para buscar. Por favor, ejecute el indexador whoosh" + +#: kallithea/controllers/search.py:144 +msgid "An error occurred during search operation." +msgstr "Ocurrió un error mientras se ejecutaba la búsqueda." + +#: kallithea/controllers/summary.py:181 +#: kallithea/templates/summary/summary.html:384 +msgid "No data ready yet" +msgstr "Todavía no hay datos disponibles" + +#: kallithea/controllers/summary.py:184 +#: kallithea/templates/summary/summary.html:98 +msgid "Statistics are disabled for this repository" +msgstr "Las estadísticas están deshabilitadas en este repositorio" + +#: kallithea/controllers/admin/auth_settings.py:135 +msgid "Auth settings updated successfully" +msgstr "Los ajustes de autentificación se han actualizado correctamente" + +#: kallithea/controllers/admin/auth_settings.py:146 +msgid "error occurred during update of auth settings" +msgstr "ocurrió un error al actualizar los ajustes de autentificación" + +#: kallithea/controllers/admin/defaults.py:97 +msgid "Default settings updated successfully" +msgstr "Los ajustes predeterminados se han actualizado correctamente" + +#: kallithea/controllers/admin/defaults.py:112 +msgid "Error occurred during update of defaults" +msgstr "Ocurrió un error al actualizar los ajustes predeterminados" + +#: kallithea/controllers/admin/gists.py:58 +#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "Para siempre" + +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/users.py:285 +msgid "5 minutes" +msgstr "5 minutos" + +#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/users.py:286 +msgid "1 hour" +msgstr "1 hora" + +#: kallithea/controllers/admin/gists.py:61 +#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/users.py:287 +msgid "1 day" +msgstr "1 día" + +#: kallithea/controllers/admin/gists.py:62 +#: kallithea/controllers/admin/my_account.py:247 +#: kallithea/controllers/admin/users.py:288 +msgid "1 month" +msgstr "1 mes" + +#: kallithea/controllers/admin/gists.py:66 +#: kallithea/controllers/admin/my_account.py:249 +#: kallithea/controllers/admin/users.py:290 +msgid "Lifetime" +msgstr "Tiempo de vida" + +#: kallithea/controllers/admin/gists.py:145 +msgid "Error occurred during gist creation" +msgstr "Ocurrió un error mientras se creaba el gist" + +#: kallithea/controllers/admin/gists.py:183 +#, python-format +msgid "Deleted gist %s" +msgstr "Gist %s eliminado" + +#: kallithea/controllers/admin/gists.py:232 +msgid "Unmodified" +msgstr "Sin modificar" + +#: kallithea/controllers/admin/gists.py:261 +msgid "Successfully updated gist content" +msgstr "Gist actualizado correctamente" + +#: kallithea/controllers/admin/gists.py:266 +msgid "Successfully updated gist data" +msgstr "" + +#: kallithea/controllers/admin/gists.py:269 +#, python-format +msgid "Error occurred during update of gist %s" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215 +#: kallithea/model/user.py:237 +msgid "You can't edit this user since it's crucial for entire application" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:129 +msgid "Your account was updated successfully" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:144 +#: kallithea/controllers/admin/users.py:201 +#, python-format +msgid "Error occurred during update of user %s" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:178 +msgid "Error occurred during update of user password" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:220 +#: kallithea/controllers/admin/users.py:414 +#, python-format +msgid "Added email %s to user" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:226 +#: kallithea/controllers/admin/users.py:420 +msgid "An error occurred during email saving" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:235 +#: kallithea/controllers/admin/users.py:432 +msgid "Removed email from user" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:259 +#: kallithea/controllers/admin/users.py:307 +msgid "API key successfully created" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:271 +#: kallithea/controllers/admin/users.py:320 +msgid "API key successfully reset" +msgstr "" + +#: kallithea/controllers/admin/my_account.py:275 +#: kallithea/controllers/admin/users.py:324 +msgid "API key successfully deleted" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:62 +#: kallithea/controllers/admin/permissions.py:66 +#: kallithea/controllers/admin/permissions.py:70 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:9 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8 +#: kallithea/templates/base/perms_summary.html:15 +msgid "Read" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:63 +#: kallithea/controllers/admin/permissions.py:67 +#: kallithea/controllers/admin/permissions.py:71 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:10 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9 +#: kallithea/templates/base/perms_summary.html:16 +msgid "Write" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:64 +#: kallithea/controllers/admin/permissions.py:68 +#: kallithea/controllers/admin/permissions.py:72 +#: kallithea/templates/admin/auth/auth_settings.html:9 +#: kallithea/templates/admin/defaults/defaults.html:9 +#: kallithea/templates/admin/permissions/permissions.html:9 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:9 +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:9 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47 +#: kallithea/templates/admin/repo_groups/repo_groups.html:10 +#: kallithea/templates/admin/repos/repo_add.html:10 +#: kallithea/templates/admin/repos/repo_add.html:14 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:11 +#: kallithea/templates/admin/repos/repos.html:9 +#: kallithea/templates/admin/settings/settings.html:9 +#: kallithea/templates/admin/user_groups/user_group_add.html:8 +#: kallithea/templates/admin/user_groups/user_group_edit.html:9 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47 +#: kallithea/templates/admin/user_groups/user_groups.html:10 +#: kallithea/templates/admin/users/user_add.html:8 +#: kallithea/templates/admin/users/user_edit.html:9 +#: kallithea/templates/admin/users/user_edit_profile.html:105 +#: kallithea/templates/admin/users/users.html:10 +#: kallithea/templates/admin/users/users.html:55 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 +#: kallithea/templates/base/perms_summary.html:17 +msgid "Admin" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:75 +#: kallithea/controllers/admin/permissions.py:86 +#: kallithea/controllers/admin/permissions.py:91 +#: kallithea/controllers/admin/permissions.py:94 +#: kallithea/controllers/admin/permissions.py:97 +#: kallithea/controllers/admin/permissions.py:100 +#: kallithea/templates/admin/auth/auth_settings.html:40 +msgid "Disabled" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:77 +msgid "Allowed with manual account activation" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:79 +msgid "Allowed with automatic account activation" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:82 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1707 +msgid "Manual activation of external account" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:83 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1708 +msgid "Automatic activation of external account" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:87 +#: kallithea/controllers/admin/permissions.py:90 +#: kallithea/controllers/admin/permissions.py:95 +#: kallithea/controllers/admin/permissions.py:98 +#: kallithea/controllers/admin/permissions.py:101 +#: kallithea/templates/admin/auth/auth_settings.html:40 +msgid "Enabled" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:124 +msgid "Global permissions updated successfully" +msgstr "" + +#: kallithea/controllers/admin/permissions.py:139 +msgid "Error occurred during update of permissions" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:187 +#, python-format +msgid "Error occurred during creation of repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:192 +#, python-format +msgid "Created repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:249 +#, python-format +msgid "Updated repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:265 +#, python-format +msgid "Error occurred during update of repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:283 +#, python-format +msgid "This group contains %s repositories and cannot be deleted" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:290 +#, python-format +msgid "This group contains %s subgroups and cannot be deleted" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:296 +#, python-format +msgid "Removed repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:301 +#, python-format +msgid "Error occurred during deletion of repository group %s" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 +#: kallithea/controllers/admin/user_groups.py:340 +msgid "Cannot revoke permission for yourself as admin" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:419 +msgid "Repository group permissions updated" +msgstr "" + +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 +#: kallithea/controllers/admin/user_groups.py:352 +msgid "An error occurred during revoking of permission" +msgstr "" + +#: kallithea/controllers/admin/repos.py:151 +#, python-format +msgid "Error creating repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:212 +#, python-format +msgid "Created repository %s from %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:221 +#, python-format +msgid "Forked repository %s as %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:224 +#, python-format +msgid "Created repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:261 +#, python-format +msgid "Repository %s updated successfully" +msgstr "" + +#: kallithea/controllers/admin/repos.py:282 +#, python-format +msgid "Error occurred during update of repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:309 +#, python-format +msgid "Detached %s forks" +msgstr "" + +#: kallithea/controllers/admin/repos.py:312 +#, python-format +msgid "Deleted %s forks" +msgstr "" + +#: kallithea/controllers/admin/repos.py:317 +#, python-format +msgid "Deleted repository %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:320 +#, python-format +msgid "Cannot delete repository %s which still has forks" +msgstr "" + +#: kallithea/controllers/admin/repos.py:325 +#, python-format +msgid "An error occurred during deletion of %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:373 +msgid "Repository permissions updated" +msgstr "" + +#: kallithea/controllers/admin/repos.py:429 +msgid "An error occurred during creation of field" +msgstr "" + +#: kallithea/controllers/admin/repos.py:443 +msgid "An error occurred during removal of field" +msgstr "" + +#: kallithea/controllers/admin/repos.py:459 +msgid "-- Not a fork --" +msgstr "" + +#: kallithea/controllers/admin/repos.py:490 +msgid "Updated repository visibility in public journal" +msgstr "" + +#: kallithea/controllers/admin/repos.py:494 +msgid "An error occurred during setting this repository in public journal" +msgstr "" + +#: kallithea/controllers/admin/repos.py:511 +msgid "Nothing" +msgstr "" + +#: kallithea/controllers/admin/repos.py:513 +#, python-format +msgid "Marked repository %s as fork of %s" +msgstr "" + +#: kallithea/controllers/admin/repos.py:520 +msgid "An error occurred during this operation" +msgstr "" + +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 +msgid "Repository has been locked" +msgstr "" + +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 +msgid "Repository has been unlocked" +msgstr "" + +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 +msgid "An error occurred during unlocking" +msgstr "" + +#: kallithea/controllers/admin/repos.py:581 +msgid "Cache invalidation successful" +msgstr "" + +#: kallithea/controllers/admin/repos.py:585 +msgid "An error occurred during cache invalidation" +msgstr "" + +#: kallithea/controllers/admin/repos.py:600 +msgid "Pulled from remote location" +msgstr "" + +#: kallithea/controllers/admin/repos.py:603 +msgid "An error occurred during pull from remote location" +msgstr "" + +#: kallithea/controllers/admin/repos.py:636 +msgid "An error occurred during deletion of repository stats" +msgstr "" + +#: kallithea/controllers/admin/settings.py:141 +msgid "Updated VCS settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:145 +msgid "Unable to activate hgsubversion support. The \"hgsubversion\" library is missing" +msgstr "" + +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 +msgid "Error occurred while updating application settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:187 +#, python-format +msgid "Repositories successfully rescanned. Added: %s. Removed: %s." +msgstr "" + +#: kallithea/controllers/admin/settings.py:244 +msgid "Updated application settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:301 +msgid "Updated visualisation settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:306 +msgid "Error occurred during updating visualisation settings" +msgstr "" + +#: kallithea/controllers/admin/settings.py:332 +msgid "Please enter email address" +msgstr "" + +#: kallithea/controllers/admin/settings.py:347 +msgid "Send email task created" +msgstr "" + +#: kallithea/controllers/admin/settings.py:378 +msgid "Added new hook" +msgstr "" + +#: kallithea/controllers/admin/settings.py:392 +msgid "Updated hooks" +msgstr "" + +#: kallithea/controllers/admin/settings.py:396 +msgid "Error occurred during hook creation" +msgstr "" + +#: kallithea/controllers/admin/settings.py:422 +msgid "Whoosh reindex task scheduled" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:150 +#, python-format +msgid "Created user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:163 +#, python-format +msgid "Error occurred during creation of user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:201 +#, python-format +msgid "Updated user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:224 +#, python-format +msgid "Error occurred during update of user group %s" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:242 +msgid "Successfully deleted user group" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:247 +msgid "An error occurred during deletion of user group" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:314 +msgid "Target group cannot be the same" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:320 +msgid "User group permissions updated" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:440 +#: kallithea/controllers/admin/users.py:383 +msgid "Updated permissions" +msgstr "" + +#: kallithea/controllers/admin/user_groups.py:444 +#: kallithea/controllers/admin/users.py:387 +msgid "An error occurred during permissions saving" +msgstr "" + +#: kallithea/controllers/admin/users.py:133 +#, python-format +msgid "Created user %s" +msgstr "" + +#: kallithea/controllers/admin/users.py:148 +#, python-format +msgid "Error occurred during creation of user %s" +msgstr "" + +#: kallithea/controllers/admin/users.py:181 +msgid "User updated successfully" +msgstr "" + +#: kallithea/controllers/admin/users.py:217 +msgid "Successfully deleted user" +msgstr "" + +#: kallithea/controllers/admin/users.py:222 +msgid "An error occurred during deletion of user" +msgstr "" + +#: kallithea/controllers/admin/users.py:235 +msgid "The default user cannot be edited" +msgstr "" + +#: kallithea/controllers/admin/users.py:462 +#, python-format +msgid "Added IP address %s to user whitelist" +msgstr "" + +#: kallithea/controllers/admin/users.py:468 +msgid "An error occurred while adding IP address" +msgstr "" + +#: kallithea/controllers/admin/users.py:482 +msgid "Removed IP address from user whitelist" +msgstr "" + +#: kallithea/lib/auth.py:737 +#, python-format +msgid "IP %s not allowed" +msgstr "" + +#: kallithea/lib/auth.py:750 +msgid "Invalid API key" +msgstr "" + +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 +msgid "You need to be a registered user to perform this action" +msgstr "" + +#: kallithea/lib/auth.py:843 +msgid "You need to be signed in to view this page" +msgstr "" + +#: kallithea/lib/base.py:493 +msgid "Repository not found in the filesystem" +msgstr "" + +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:618 +msgid "Changeset not found" +msgstr "" + +#: kallithea/lib/diffs.py:66 +msgid "Binary file" +msgstr "" + +#: kallithea/lib/diffs.py:82 +msgid "Changeset was too big and was cut off, use diff menu to display this diff" +msgstr "" + +#: kallithea/lib/diffs.py:92 +msgid "No changes detected" +msgstr "" + +#: kallithea/lib/helpers.py:605 +#, python-format +msgid "Deleted branch: %s" +msgstr "" + +#: kallithea/lib/helpers.py:607 +#, python-format +msgid "Created tag: %s" +msgstr "" + +#: kallithea/lib/helpers.py:667 +#, python-format +msgid "Show all combined changesets %s->%s" +msgstr "" + +#: kallithea/lib/helpers.py:673 +msgid "Compare view" +msgstr "" + +#: kallithea/lib/helpers.py:692 +msgid "and" +msgstr "" + +#: kallithea/lib/helpers.py:693 +#, python-format +msgid "%s more" +msgstr "" + +#: kallithea/lib/helpers.py:694 kallithea/templates/changelog/changelog.html:44 +msgid "revisions" +msgstr "" + +#: kallithea/lib/helpers.py:718 +#, python-format +msgid "Fork name %s" +msgstr "" + +#: kallithea/lib/helpers.py:738 +#, python-format +msgid "Pull request %s" +msgstr "" + +#: kallithea/lib/helpers.py:748 +msgid "[deleted] repository" +msgstr "" + +#: kallithea/lib/helpers.py:750 kallithea/lib/helpers.py:762 +msgid "[created] repository" +msgstr "" + +#: kallithea/lib/helpers.py:752 +msgid "[created] repository as fork" +msgstr "" + +#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:764 +msgid "[forked] repository" +msgstr "" + +#: kallithea/lib/helpers.py:756 kallithea/lib/helpers.py:766 +msgid "[updated] repository" +msgstr "" + +#: kallithea/lib/helpers.py:758 +msgid "[downloaded] archive from repository" +msgstr "" + +#: kallithea/lib/helpers.py:760 +msgid "[delete] repository" +msgstr "" + +#: kallithea/lib/helpers.py:768 +msgid "[created] user" +msgstr "" + +#: kallithea/lib/helpers.py:770 +msgid "[updated] user" +msgstr "" + +#: kallithea/lib/helpers.py:772 +msgid "[created] user group" +msgstr "" + +#: kallithea/lib/helpers.py:774 +msgid "[updated] user group" +msgstr "" + +#: kallithea/lib/helpers.py:776 +msgid "[commented] on revision in repository" +msgstr "" + +#: kallithea/lib/helpers.py:778 +msgid "[commented] on pull request for" +msgstr "" + +#: kallithea/lib/helpers.py:780 +msgid "[closed] pull request for" +msgstr "" + +#: kallithea/lib/helpers.py:782 +msgid "[pushed] into" +msgstr "" + +#: kallithea/lib/helpers.py:784 +msgid "[committed via Kallithea] into repository" +msgstr "" + +#: kallithea/lib/helpers.py:786 +msgid "[pulled from remote] into repository" +msgstr "" + +#: kallithea/lib/helpers.py:788 +msgid "[pulled] from" +msgstr "" + +#: kallithea/lib/helpers.py:790 +msgid "[started following] repository" +msgstr "" + +#: kallithea/lib/helpers.py:792 +msgid "[stopped following] repository" +msgstr "" + +#: kallithea/lib/helpers.py:1119 +#, python-format +msgid " and %s more" +msgstr "" + +#: kallithea/lib/helpers.py:1123 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 +msgid "No files" +msgstr "" + +#: kallithea/lib/helpers.py:1189 +msgid "new file" +msgstr "" + +#: kallithea/lib/helpers.py:1192 +msgid "mod" +msgstr "" + +#: kallithea/lib/helpers.py:1195 +msgid "del" +msgstr "" + +#: kallithea/lib/helpers.py:1198 +msgid "rename" +msgstr "" + +#: kallithea/lib/helpers.py:1203 +msgid "chmod" +msgstr "" + +#: kallithea/lib/helpers.py:1464 +#, python-format +msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories" +msgstr "" + +#: kallithea/lib/utils2.py:434 +#, python-format +msgid "%d year" +msgid_plural "%d years" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:435 +#, python-format +msgid "%d month" +msgid_plural "%d months" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:436 +#, python-format +msgid "%d day" +msgid_plural "%d days" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:437 +#, python-format +msgid "%d hour" +msgid_plural "%d hours" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:438 +#, python-format +msgid "%d minute" +msgid_plural "%d minutes" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:439 +#, python-format +msgid "%d second" +msgid_plural "%d seconds" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/lib/utils2.py:455 +#, python-format +msgid "in %s" +msgstr "" + +#: kallithea/lib/utils2.py:457 +#, python-format +msgid "%s ago" +msgstr "" + +#: kallithea/lib/utils2.py:459 +#, python-format +msgid "in %s and %s" +msgstr "" + +#: kallithea/lib/utils2.py:462 +#, python-format +msgid "%s and %s ago" +msgstr "" + +#: kallithea/lib/utils2.py:465 +msgid "just now" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649 +msgid "Repository no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650 +msgid "Repository read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651 +msgid "Repository write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652 +msgid "Repository admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308 +msgid "Repository Group no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309 +msgid "Repository Group read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310 +msgid "Repository Group write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311 +msgid "Repository Group admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1671 +msgid "Kallithea Administrator" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670 +msgid "Repository creation disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671 +msgid "Repository creation enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675 +msgid "Repository forking disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676 +msgid "Repository forking enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403 +msgid "Register disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404 +msgid "Register new user with Kallithea with manual activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407 +msgid "Register new user with Kallithea with auto activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200 +msgid "Not Reviewed" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2238 +msgid "Approved" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2239 +msgid "Rejected" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626 +#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653 +#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766 +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203 +msgid "Under Review" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1520 +msgid "top level" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654 +msgid "Repository group no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655 +msgid "Repository group read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656 +msgid "Repository group write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396 +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657 +msgid "Repository group admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659 +msgid "User group no access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660 +msgid "User group read access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661 +msgid "User group write access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662 +msgid "User group admin access" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664 +msgid "Repository Group creation disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665 +msgid "Repository Group creation enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667 +msgid "User Group creation disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668 +msgid "User Group creation enabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1703 +msgid "Registration disabled" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679 +msgid "User Registration with manual account activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437 +#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483 +#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540 +#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541 +#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562 +#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601 +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680 +msgid "User Registration with automatic account activation" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1697 +msgid "Repository creation enabled with write permission to a repository group" +msgstr "" + +#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1698 +msgid "Repository creation disabled with write permission to a repository group" +msgstr "" + +#: kallithea/model/comment.py:72 +#, python-format +msgid "on line %s" +msgstr "" + +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 +msgid "[Mention]" +msgstr "" + +#: kallithea/model/db.py:1673 +msgid "Default user has no access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1674 +msgid "Default user has read access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1675 +msgid "Default user has write access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1678 +msgid "Default user has no access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1679 +msgid "Default user has read access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1680 +msgid "Default user has write access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1683 +msgid "Default user has no access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has read access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1685 +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1686 +msgid "Default user has admin access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1688 +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1689 +msgid "Non-admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1691 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1694 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1695 +msgid "Non-admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1700 +msgid "Only admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1701 +msgid "Non-admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1704 +msgid "User registration with manual account activation" +msgstr "" + +#: kallithea/model/db.py:1705 +msgid "User registration with automatic account activation" +msgstr "" + +#: kallithea/model/db.py:2237 +msgid "Not reviewed" +msgstr "" + +#: kallithea/model/db.py:2240 +msgid "Under review" +msgstr "" + +#: kallithea/model/forms.py:57 +msgid "Please enter a login" +msgstr "" + +#: kallithea/model/forms.py:58 +#, python-format +msgid "Enter a value %(min)i characters long or more" +msgstr "" + +#: kallithea/model/forms.py:66 +msgid "Please enter a password" +msgstr "" + +#: kallithea/model/forms.py:67 +#, python-format +msgid "Enter %(min)i characters or more" +msgstr "" + +#: kallithea/model/forms.py:165 +msgid "Name must not contain only digits" +msgstr "" + +#: kallithea/model/notification.py:254 +#, python-format +msgid "%(user)s commented on changeset %(age)s" +msgstr "" + +#: kallithea/model/notification.py:255 +#, python-format +msgid "%(user)s sent message %(age)s" +msgstr "" + +#: kallithea/model/notification.py:256 +#, python-format +msgid "%(user)s mentioned you %(age)s" +msgstr "" + +#: kallithea/model/notification.py:257 +#, python-format +msgid "%(user)s registered in Kallithea %(age)s" +msgstr "" + +#: kallithea/model/notification.py:258 +#, python-format +msgid "%(user)s opened new pull request %(age)s" +msgstr "" + +#: kallithea/model/notification.py:259 +#, python-format +msgid "%(user)s commented on pull request %(age)s" +msgstr "" + +#: kallithea/model/notification.py:266 +#, python-format +msgid "%(user)s commented on changeset at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:267 +#, python-format +msgid "%(user)s sent message at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:268 +#, python-format +msgid "%(user)s mentioned you at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:269 +#, python-format +msgid "%(user)s registered in Kallithea at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:270 +#, python-format +msgid "%(user)s opened new pull request at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:271 +#, python-format +msgid "%(user)s commented on pull request at %(when)s" +msgstr "" + +#: kallithea/model/notification.py:302 +#, python-format +msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s" +msgstr "" + +#: kallithea/model/notification.py:305 +#, python-format +msgid "New user %(new_username)s registered" +msgstr "" + +#: kallithea/model/notification.py:307 +#, python-format +msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" +msgstr "" + +#: kallithea/model/notification.py:308 +#, python-format +msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" +msgstr "" + +#: kallithea/model/notification.py:321 +msgid "Closing" +msgstr "" + +#: kallithea/model/pull_request.py:137 +#, python-format +msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" +msgstr "" + +#: kallithea/model/scm.py:812 +msgid "latest tip" +msgstr "" + +#: kallithea/model/user.py:192 +msgid "New user registration" +msgstr "" + +#: kallithea/model/user.py:256 +msgid "You can't remove this user since it is crucial for the entire application" +msgstr "" + +#: kallithea/model/user.py:261 +#, python-format +msgid "User \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories: %s" +msgstr "" + +#: kallithea/model/user.py:266 +#, python-format +msgid "User \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups: %s" +msgstr "" + +#: kallithea/model/user.py:273 +#, python-format +msgid "User \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups: %s" +msgstr "" + +#: kallithea/model/user.py:368 +msgid "Password reset link" +msgstr "" + +#: kallithea/model/user.py:418 +msgid "Password reset notification" +msgstr "" + +#: kallithea/model/user.py:419 +#, python-format +msgid "The password to your account %s has been changed using password reset form." +msgstr "" + +#: kallithea/model/validators.py:77 kallithea/model/validators.py:78 +msgid "Value cannot be an empty list" +msgstr "" + +#: kallithea/model/validators.py:96 +#, python-format +msgid "Username \"%(username)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:98 +#, python-format +msgid "Username \"%(username)s\" cannot be used" +msgstr "" + +#: kallithea/model/validators.py:100 +msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with an alphanumeric character or underscore" +msgstr "" + +#: kallithea/model/validators.py:127 +msgid "The input is not valid" +msgstr "" + +#: kallithea/model/validators.py:134 +#, python-format +msgid "Username %(username)s is not valid" +msgstr "" + +#: kallithea/model/validators.py:154 +msgid "Invalid user group name" +msgstr "" + +#: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 +msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character" +msgstr "" + +#: kallithea/model/validators.py:197 +msgid "Cannot assign this group as parent" +msgstr "" + +#: kallithea/model/validators.py:198 +#, python-format +msgid "Group \"%(group_name)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:200 +#, python-format +msgid "Repository with name \"%(group_name)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:258 +msgid "Invalid characters (non-ascii) in password" +msgstr "" + +#: kallithea/model/validators.py:273 +msgid "Invalid old password" +msgstr "" + +#: kallithea/model/validators.py:289 +msgid "Passwords do not match" +msgstr "" + +#: kallithea/model/validators.py:304 +msgid "Invalid username or password" +msgstr "" + +#: kallithea/model/validators.py:335 +msgid "Token mismatch" +msgstr "" + +#: kallithea/model/validators.py:351 +#, python-format +msgid "Repository name %(repo)s is not allowed" +msgstr "" + +#: kallithea/model/validators.py:353 +#, python-format +msgid "Repository named %(repo)s already exists" +msgstr "" + +#: kallithea/model/validators.py:354 +#, python-format +msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" +msgstr "" + +#: kallithea/model/validators.py:356 +#, python-format +msgid "Repository group with name \"%(repo)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:470 +msgid "Invalid repository URL" +msgstr "" + +#: kallithea/model/validators.py:471 +msgid "Invalid repository URL. It must be a valid http, https, ssh, svn+http or svn+https URL" +msgstr "" + +#: kallithea/model/validators.py:496 +msgid "Fork has to be the same type as parent" +msgstr "" + +#: kallithea/model/validators.py:511 +msgid "You don't have permissions to create repository in this group" +msgstr "" + +#: kallithea/model/validators.py:513 +msgid "no permission to create repository in root location" +msgstr "" + +#: kallithea/model/validators.py:563 +msgid "You don't have permissions to create a group in this location" +msgstr "" + +#: kallithea/model/validators.py:604 +msgid "This username or user group name is not valid" +msgstr "" + +#: kallithea/model/validators.py:697 +msgid "This is not a valid path" +msgstr "" + +#: kallithea/model/validators.py:714 +msgid "This email address is already in use" +msgstr "" + +#: kallithea/model/validators.py:734 +#, python-format +msgid "Email address \"%(email)s\" not found" +msgstr "" + +#: kallithea/model/validators.py:771 +msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\"" +msgstr "" + +#: kallithea/model/validators.py:783 +msgid "Please enter a valid IPv4 or IPv6 address" +msgstr "" + +#: kallithea/model/validators.py:784 +#, python-format +msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" +msgstr "" + +#: kallithea/model/validators.py:817 +msgid "Key name can only consist of letters, underscore, dash or numbers" +msgstr "" + +#: kallithea/model/validators.py:831 +msgid "Filename cannot be inside a directory" +msgstr "" + +#: kallithea/model/validators.py:847 +#, python-format +msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" +msgstr "" + +#: kallithea/templates/about.html:4 kallithea/templates/about.html:17 +msgid "About" +msgstr "" + +#: kallithea/templates/index.html:5 +msgid "Dashboard" +msgstr "" + +#: kallithea/templates/index_base.html:6 +#: kallithea/templates/admin/my_account/my_account_repos.html:3 +#: kallithea/templates/admin/my_account/my_account_watched.html:3 +#: kallithea/templates/admin/repo_groups/repo_groups.html:9 +#: kallithea/templates/admin/repos/repos.html:9 +#: kallithea/templates/admin/user_groups/user_groups.html:9 +#: kallithea/templates/admin/users/users.html:9 +#: kallithea/templates/bookmarks/bookmarks.html:9 +#: kallithea/templates/branches/branches.html:9 +#: kallithea/templates/journal/journal.html:9 +#: kallithea/templates/journal/journal.html:48 +#: kallithea/templates/journal/journal.html:49 +#: kallithea/templates/tags/tags.html:9 +msgid "quick filter..." +msgstr "" + +#: kallithea/templates/index_base.html:6 +msgid "repositories" +msgstr "" + +#: kallithea/templates/index_base.html:20 +#: kallithea/templates/index_base.html:25 +#: kallithea/templates/admin/repos/repo_add.html:5 +#: kallithea/templates/admin/repos/repo_add.html:19 +#: kallithea/templates/admin/repos/repos.html:22 +msgid "Add Repository" +msgstr "" + +#: kallithea/templates/index_base.html:22 +#: kallithea/templates/index_base.html:27 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:5 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:13 +#: kallithea/templates/admin/repo_groups/repo_groups.html:26 +msgid "Add Repository Group" +msgstr "" + +#: kallithea/templates/index_base.html:32 +msgid "You have admin right to this group, and can edit it" +msgstr "" + +#: kallithea/templates/index_base.html:32 +msgid "Edit Repository Group" +msgstr "" + +#: kallithea/templates/index_base.html:45 +msgid "Group Name" +msgstr "" + +#: kallithea/templates/index_base.html:46 +#: kallithea/templates/index_base.html:127 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:64 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:42 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17 +#: kallithea/templates/admin/repo_groups/repo_groups.html:47 +#: kallithea/templates/admin/repos/repo_add_base.html:28 +#: kallithea/templates/admin/repos/repo_edit_settings.html:65 +#: kallithea/templates/admin/repos/repos.html:48 +#: kallithea/templates/admin/user_groups/user_group_add.html:40 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15 +#: kallithea/templates/admin/user_groups/user_groups.html:47 +#: kallithea/templates/admin/users/user_edit_api_keys.html:64 +#: kallithea/templates/email_templates/changeset_comment.html:18 +#: kallithea/templates/email_templates/pull_request.html:12 +#: kallithea/templates/forks/fork.html:38 +#: kallithea/templates/pullrequests/pullrequest.html:40 +#: kallithea/templates/pullrequests/pullrequest_show.html:38 +#: kallithea/templates/pullrequests/pullrequest_show.html:63 +#: kallithea/templates/summary/summary.html:85 +msgid "Description" +msgstr "" + +#: kallithea/templates/index_base.html:125 +#: kallithea/templates/admin/my_account/my_account_repos.html:46 +#: kallithea/templates/admin/my_account/my_account_watched.html:46 +#: kallithea/templates/admin/repo_groups/repo_groups.html:46 +#: kallithea/templates/admin/repos/repo_add_base.html:9 +#: kallithea/templates/admin/repos/repo_edit_settings.html:7 +#: kallithea/templates/admin/repos/repos.html:47 +#: kallithea/templates/admin/user_groups/user_groups.html:46 +#: kallithea/templates/base/perms_summary.html:53 +#: kallithea/templates/bookmarks/bookmarks.html:49 +#: kallithea/templates/bookmarks/bookmarks_data.html:7 +#: kallithea/templates/branches/branches.html:49 +#: kallithea/templates/branches/branches_data.html:7 +#: kallithea/templates/files/files_browser.html:60 +#: kallithea/templates/journal/journal.html:187 +#: kallithea/templates/journal/journal.html:278 +#: kallithea/templates/tags/tags.html:49 +#: kallithea/templates/tags/tags_data.html:7 +msgid "Name" +msgstr "" + +#: kallithea/templates/index_base.html:128 +msgid "Last Change" +msgstr "" + +#: kallithea/templates/index_base.html:130 +#: kallithea/templates/admin/my_account/my_account_repos.html:48 +#: kallithea/templates/admin/my_account/my_account_watched.html:48 +#: kallithea/templates/admin/repos/repos.html:49 +#: kallithea/templates/journal/journal.html:189 +#: kallithea/templates/journal/journal.html:280 +msgid "Tip" +msgstr "" + +#: kallithea/templates/index_base.html:132 +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10 +#: kallithea/templates/admin/repo_groups/repo_groups.html:49 +#: kallithea/templates/admin/repos/repo_edit_settings.html:53 +#: kallithea/templates/admin/repos/repos.html:50 +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8 +#: kallithea/templates/admin/user_groups/user_groups.html:50 +#: kallithea/templates/pullrequests/pullrequest_data.html:16 +#: kallithea/templates/pullrequests/pullrequest_show.html:156 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 +#: kallithea/templates/summary/summary.html:134 +msgid "Owner" +msgstr "" + +#: kallithea/templates/index_base.html:140 +#: kallithea/templates/admin/my_account/my_account_repos.html:57 +#: kallithea/templates/admin/my_account/my_account_watched.html:57 +#: kallithea/templates/base/root.html:43 +#: kallithea/templates/bookmarks/bookmarks.html:79 +#: kallithea/templates/branches/branches.html:79 +#: kallithea/templates/journal/journal.html:198 +#: kallithea/templates/journal/journal.html:289 +#: kallithea/templates/tags/tags.html:79 +msgid "Click to sort ascending" +msgstr "" + +#: kallithea/templates/index_base.html:141 +#: kallithea/templates/admin/my_account/my_account_repos.html:58 +#: kallithea/templates/admin/my_account/my_account_watched.html:58 +#: kallithea/templates/base/root.html:44 +#: kallithea/templates/bookmarks/bookmarks.html:80 +#: kallithea/templates/branches/branches.html:80 +#: kallithea/templates/journal/journal.html:199 +#: kallithea/templates/journal/journal.html:290 +#: kallithea/templates/tags/tags.html:80 +msgid "Click to sort descending" +msgstr "" + +#: kallithea/templates/index_base.html:142 +msgid "No repositories found." +msgstr "" + +#: kallithea/templates/index_base.html:143 +#: kallithea/templates/admin/my_account/my_account_repos.html:60 +#: kallithea/templates/admin/my_account/my_account_watched.html:60 +#: kallithea/templates/base/root.html:46 +#: kallithea/templates/bookmarks/bookmarks.html:82 +#: kallithea/templates/branches/branches.html:82 +#: kallithea/templates/journal/journal.html:201 +#: kallithea/templates/journal/journal.html:292 +#: kallithea/templates/tags/tags.html:82 +msgid "Data error." +msgstr "" + +#: kallithea/templates/index_base.html:144 +#: kallithea/templates/admin/my_account/my_account_repos.html:61 +#: kallithea/templates/admin/my_account/my_account_watched.html:61 +#: kallithea/templates/base/root.html:47 +#: kallithea/templates/bookmarks/bookmarks.html:83 +#: kallithea/templates/branches/branches.html:83 +#: kallithea/templates/journal/journal.html:202 +#: kallithea/templates/journal/journal.html:293 +#: kallithea/templates/tags/tags.html:83 +msgid "Loading..." +msgstr "" + +#: kallithea/templates/login.html:5 kallithea/templates/login.html:15 +#: kallithea/templates/base/base.html:414 +msgid "Log In" +msgstr "" + +#: kallithea/templates/login.html:13 +#, python-format +msgid "Log In to %s" +msgstr "" + +#: kallithea/templates/login.html:26 kallithea/templates/register.html:24 +#: kallithea/templates/admin/admin_log.html:5 +#: kallithea/templates/admin/my_account/my_account_profile.html:25 +#: kallithea/templates/admin/users/user_add.html:32 +#: kallithea/templates/admin/users/user_edit_profile.html:24 +#: kallithea/templates/admin/users/users.html:50 +#: kallithea/templates/base/base.html:390 +#: kallithea/templates/pullrequests/pullrequest_show.html:166 +msgid "Username" +msgstr "" + +#: kallithea/templates/login.html:33 kallithea/templates/register.html:33 +#: kallithea/templates/admin/my_account/my_account.html:37 +#: kallithea/templates/admin/users/user_add.html:41 +#: kallithea/templates/base/base.html:399 +msgid "Password" +msgstr "" + +#: kallithea/templates/login.html:44 +msgid "Remember me" +msgstr "" + +#: kallithea/templates/login.html:53 +msgid "Forgot your password ?" +msgstr "" + +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 +msgid "Don't have an account ?" +msgstr "" + +#: kallithea/templates/login.html:59 +msgid "Sign In" +msgstr "" + +#: kallithea/templates/password_reset.html:5 +msgid "Password Reset" +msgstr "" + +#: kallithea/templates/password_reset.html:12 +#: kallithea/templates/password_reset_confirmation.html:12 +#, python-format +msgid "Reset Your Password to %s" +msgstr "" + +#: kallithea/templates/password_reset.html:14 +#: kallithea/templates/password_reset_confirmation.html:5 +#: kallithea/templates/password_reset_confirmation.html:14 +msgid "Reset Your Password" +msgstr "" + +#: kallithea/templates/password_reset.html:25 +msgid "Email Address" +msgstr "" + +#: kallithea/templates/password_reset.html:35 +#: kallithea/templates/register.html:79 +msgid "Captcha" +msgstr "" + +#: kallithea/templates/password_reset.html:46 +msgid "Send Password Reset Email" +msgstr "" + +#: kallithea/templates/password_reset.html:47 +msgid "A password reset link will be sent to the specified email address if it is registered in the system." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:19 +#, python-format +msgid "You are about to set a new password for the email address %s." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:20 +msgid "Note that you must use the same browser session for this as the one used to request the password reset." +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:30 +msgid "Code you received in the email" +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:39 +msgid "New Password" +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:48 +msgid "Confirm New Password" +msgstr "" + +#: kallithea/templates/password_reset_confirmation.html:56 +msgid "Confirm" +msgstr "" + +#: kallithea/templates/register.html:5 kallithea/templates/register.html:14 +#: kallithea/templates/register.html:90 +msgid "Sign Up" +msgstr "" + +#: kallithea/templates/register.html:12 +#, python-format +msgid "Sign Up to %s" +msgstr "" + +#: kallithea/templates/register.html:42 +msgid "Re-enter password" +msgstr "" + +#: kallithea/templates/register.html:51 +#: kallithea/templates/admin/my_account/my_account_profile.html:34 +#: kallithea/templates/admin/users/user_add.html:59 +#: kallithea/templates/admin/users/user_edit_profile.html:78 +#: kallithea/templates/admin/users/users.html:51 +msgid "First Name" +msgstr "" + +#: kallithea/templates/register.html:60 +#: kallithea/templates/admin/my_account/my_account_profile.html:43 +#: kallithea/templates/admin/users/user_add.html:68 +#: kallithea/templates/admin/users/user_edit_profile.html:87 +#: kallithea/templates/admin/users/users.html:52 +msgid "Last Name" +msgstr "" + +#: kallithea/templates/register.html:69 +#: kallithea/templates/admin/my_account/my_account_profile.html:52 +#: kallithea/templates/admin/settings/settings.html:31 +#: kallithea/templates/admin/users/user_add.html:77 +#: kallithea/templates/admin/users/user_edit_profile.html:33 +msgid "Email" +msgstr "" + +#: kallithea/templates/register.html:92 +msgid "Registered accounts are ready to use and need no further action." +msgstr "" + +#: kallithea/templates/register.html:94 +msgid "Please wait for an administrator to activate your account." +msgstr "" + +#: kallithea/templates/switch_to_list.html:10 +#: kallithea/templates/branches/branches_data.html:69 +msgid "There are no branches yet" +msgstr "" + +#: kallithea/templates/switch_to_list.html:32 +#: kallithea/templates/tags/tags_data.html:44 +msgid "There are no tags yet" +msgstr "" + +#: kallithea/templates/switch_to_list.html:45 +#: kallithea/templates/bookmarks/bookmarks_data.html:43 +msgid "There are no bookmarks yet" +msgstr "" + +#: kallithea/templates/admin/admin.html:5 +#: kallithea/templates/admin/admin.html:13 +#: kallithea/templates/base/base.html:59 +msgid "Admin Journal" +msgstr "" + +#: kallithea/templates/admin/admin.html:10 +msgid "journal filter..." +msgstr "" + +#: kallithea/templates/admin/admin.html:12 +#: kallithea/templates/journal/journal.html:11 +msgid "Filter" +msgstr "" + +#: kallithea/templates/admin/admin.html:13 +#: kallithea/templates/journal/journal.html:12 +#, python-format +msgid "%s Entry" +msgid_plural "%s Entries" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/admin/admin_log.html:6 +#: kallithea/templates/admin/my_account/my_account_repos.html:50 +#: kallithea/templates/admin/my_account/my_account_watched.html:50 +#: kallithea/templates/admin/repo_groups/repo_groups.html:50 +#: kallithea/templates/admin/repos/repo_edit_fields.html:8 +#: kallithea/templates/admin/repos/repos.html:52 +#: kallithea/templates/admin/user_groups/user_groups.html:51 +#: kallithea/templates/admin/users/users.html:57 +#: kallithea/templates/journal/journal.html:191 +#: kallithea/templates/journal/journal.html:282 +msgid "Action" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:7 +#: kallithea/templates/admin/permissions/permissions_globals.html:18 +msgid "Repository" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:8 +#: kallithea/templates/bookmarks/bookmarks.html:51 +#: kallithea/templates/bookmarks/bookmarks_data.html:9 +#: kallithea/templates/branches/branches.html:51 +#: kallithea/templates/branches/branches_data.html:9 +#: kallithea/templates/tags/tags.html:51 +#: kallithea/templates/tags/tags_data.html:9 +msgid "Date" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:9 +msgid "From IP" +msgstr "" + +#: kallithea/templates/admin/admin_log.html:63 +msgid "No actions yet" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:5 +msgid "Authentication Settings" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:11 +#: kallithea/templates/base/base.html:65 +msgid "Authentication" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:28 +msgid "Authentication Plugins" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:31 +msgid "Enabled Plugins" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:33 +msgid "Comma-separated list of plugins; Kallithea will try user authentication in plugin order" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:34 +msgid "Available built-in plugins" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:51 +msgid "Plugin" +msgstr "" + +#: kallithea/templates/admin/auth/auth_settings.html:101 +#: kallithea/templates/admin/defaults/defaults.html:82 +#: kallithea/templates/admin/my_account/my_account_password.html:36 +#: kallithea/templates/admin/my_account/my_account_profile.html:60 +#: kallithea/templates/admin/permissions/permissions_globals.html:112 +#: kallithea/templates/admin/repo_groups/repo_group_add.html:69 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:101 +#: kallithea/templates/admin/repos/repo_edit_settings.html:127 +#: kallithea/templates/admin/settings/settings_hooks.html:53 +#: kallithea/templates/admin/user_groups/user_group_add.html:57 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60 +#: kallithea/templates/admin/users/user_add.html:96 +#: kallithea/templates/admin/users/user_edit_profile.html:113 +#: kallithea/templates/base/default_perms_box.html:64 +msgid "Save" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:5 +#: kallithea/templates/admin/defaults/defaults.html:11 +#: kallithea/templates/base/base.html:66 +msgid "Repository Defaults" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:33 +#: kallithea/templates/admin/repos/repo_add_base.html:55 +#: kallithea/templates/admin/repos/repo_edit_fields.html:7 +msgid "Type" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:42 +#: kallithea/templates/admin/repos/repo_add_base.html:73 +#: kallithea/templates/admin/repos/repo_edit_settings.html:75 +#: kallithea/templates/data_table/_dt_elements.html:72 +msgid "Private repository" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:46 +#: kallithea/templates/admin/repos/repo_add_base.html:77 +#: kallithea/templates/admin/repos/repo_edit_settings.html:79 +#: kallithea/templates/forks/fork.html:72 +msgid "Private repositories are only visible to people explicitly added as collaborators." +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:53 +#: kallithea/templates/admin/repos/repo_edit_settings.html:84 +msgid "Enable statistics" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:57 +#: kallithea/templates/admin/repos/repo_edit_settings.html:88 +msgid "Enable statistics window on summary page." +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:63 +#: kallithea/templates/admin/repos/repo_edit_settings.html:93 +msgid "Enable downloads" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:67 +#: kallithea/templates/admin/repos/repo_edit_settings.html:97 +msgid "Enable download menu on summary page." +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:73 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34 +#: kallithea/templates/admin/repos/repo_edit_settings.html:102 +msgid "Enable locking" +msgstr "" + +#: kallithea/templates/admin/defaults/defaults.html:77 +#: kallithea/templates/admin/repos/repo_edit_settings.html:106 +msgid "Enable lock-by-pulling on repository." +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:5 +#: kallithea/templates/admin/gists/edit.html:18 +msgid "Edit Gist" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:36 +#, python-format +msgid "Gist was update since you started editing. Copy your changes and click %(here)s to reload new version." +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:55 +#: kallithea/templates/admin/gists/new.html:39 +msgid "Gist description ..." +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:57 +#: kallithea/templates/admin/gists/new.html:41 +msgid "Gist lifetime" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:61 +#: kallithea/templates/admin/gists/edit.html:63 +#: kallithea/templates/admin/gists/index.html:57 +#: kallithea/templates/admin/gists/index.html:59 +#: kallithea/templates/admin/gists/show.html:47 +#: kallithea/templates/admin/gists/show.html:49 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:8 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:27 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:32 +#: kallithea/templates/admin/users/user_edit_api_keys.html:8 +#: kallithea/templates/admin/users/user_edit_api_keys.html:27 +#: kallithea/templates/admin/users/user_edit_api_keys.html:32 +msgid "Expires" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:61 +#: kallithea/templates/admin/gists/index.html:57 +#: kallithea/templates/admin/gists/show.html:47 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:8 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:27 +#: kallithea/templates/admin/users/user_edit_api_keys.html:8 +#: kallithea/templates/admin/users/user_edit_api_keys.html:27 +msgid "Never" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:146 +msgid "Update Gist" +msgstr "" + +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 +msgid "Cancel" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:6 +#: kallithea/templates/admin/gists/index.html:16 +#, python-format +msgid "Private Gists for User %s" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:8 +#: kallithea/templates/admin/gists/index.html:18 +#, python-format +msgid "Public Gists for User %s" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:10 +#: kallithea/templates/admin/gists/index.html:20 +msgid "Public Gists" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:37 +#: kallithea/templates/admin/gists/show.html:25 +#: kallithea/templates/base/base.html:321 +msgid "Create New Gist" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:54 +#: kallithea/templates/data_table/_dt_elements.html:141 +msgid "Created" +msgstr "" + +#: kallithea/templates/admin/gists/index.html:74 +msgid "There are no gists yet" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:5 +#: kallithea/templates/admin/gists/new.html:18 +msgid "New Gist" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:47 +msgid "name this file..." +msgstr "" + +#: kallithea/templates/admin/gists/new.html:56 +msgid "Create Private Gist" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:57 +msgid "Create Public Gist" +msgstr "" + +#: kallithea/templates/admin/gists/new.html:58 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:15 +#: kallithea/templates/admin/my_account/my_account_api_keys.html:70 +#: kallithea/templates/admin/my_account/my_account_emails.html:46 +#: kallithea/templates/admin/my_account/my_account_password.html:37 +#: kallithea/templates/admin/my_account/my_account_profile.html:61 +#: kallithea/templates/admin/permissions/permissions_globals.html:113 +#: kallithea/templates/admin/permissions/permissions_ips.html:39 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43 +#: kallithea/templates/admin/repos/repo_edit_fields.html:59 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:102 +#: kallithea/templates/admin/repos/repo_edit_settings.html:128 +#: kallithea/templates/admin/settings/settings_global.html:57 +#: kallithea/templates/admin/settings/settings_vcs.html:81 +#: kallithea/templates/admin/settings/settings_visual.html:117 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105 +#: kallithea/templates/admin/users/user_edit_api_keys.html:15 +#: kallithea/templates/admin/users/user_edit_api_keys.html:70 +#: kallithea/templates/admin/users/user_edit_emails.html:46 +#: kallithea/templates/admin/users/user_edit_ips.html:50 +#: kallithea/templates/admin/users/user_edit_profile.html:114 +#: kallithea/templates/base/default_perms_box.html:65 +#: kallithea/templates/files/files_add.html:65 +#: kallithea/templates/files/files_delete.html:44 +#: kallithea/templates/files/files_edit.html:68 +#: kallithea/templates/pullrequests/pullrequest.html:89 +msgid "Reset" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:5 +#: kallithea/templates/admin/gists/show.html:9 +msgid "Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:10 +#: kallithea/templates/email_templates/changeset_comment.html:15 +#: kallithea/templates/email_templates/pull_request.html:10 +#: kallithea/templates/email_templates/pull_request_comment.html:15 +msgid "URL" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:37 +msgid "Public Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:39 +msgid "Private Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:56 +#: kallithea/templates/admin/my_account/my_account_emails.html:19 +#: kallithea/templates/admin/permissions/permissions_ips.html:12 +#: kallithea/templates/admin/repos/repo_edit_advanced.html:75 +#: kallithea/templates/admin/repos/repo_edit_fields.html:18 +#: kallithea/templates/admin/settings/settings_hooks.html:36 +#: kallithea/templates/admin/users/user_edit_emails.html:19 +#: kallithea/templates/admin/users/user_edit_ips.html:22 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 +#: kallithea/templates/data_table/_dt_elements.html:129 +#: kallithea/templates/data_table/_dt_elements.html:157 +#: kallithea/templates/data_table/_dt_elements.html:173 +#: kallithea/templates/data_table/_dt_elements.html:189 +#: kallithea/templates/files/files_source.html:39 +#: kallithea/templates/files/files_source.html:42 +#: kallithea/templates/files/files_source.html:45 +#: kallithea/templates/pullrequests/pullrequest_data.html:20 +msgid "Delete" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:56 +msgid "Confirm to delete this Gist" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:63 +#: kallithea/templates/base/perms_summary.html:43 +#: kallithea/templates/base/perms_summary.html:79 +#: kallithea/templates/base/perms_summary.html:81 +#: kallithea/templates/data_table/_dt_elements.html:122 +#: kallithea/templates/data_table/_dt_elements.html:123 +#: kallithea/templates/data_table/_dt_elements.html:150 +#: kallithea/templates/data_table/_dt_elements.html:151 +#: kallithea/templates/data_table/_dt_elements.html:165 +#: kallithea/templates/data_table/_dt_elements.html:167 +#: kallithea/templates/data_table/_dt_elements.html:181 +#: kallithea/templates/data_table/_dt_elements.html:183 +#: kallithea/templates/files/diff_2way.html:56 +#: kallithea/templates/files/files_source.html:41 +#: kallithea/templates/files/files_source.html:44 +#: kallithea/templates/pullrequests/pullrequest_show.html:41 +msgid "Edit" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:65 +#: kallithea/templates/files/files_edit.html:49 +#: kallithea/templates/files/files_source.html:34 +msgid "Show as Raw" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:73 +msgid "created" +msgstr "" + +#: kallithea/templates/admin/gists/show.html:86 +msgid "Show as raw" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:5 +#: kallithea/templates/admin/my_account/my_account.html:9 +#: kallithea/templates/base/base.html:431 +msgid "My Account" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:35 +#: kallithea/templates/admin/users/user_edit.html:29 +msgid "Profile" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:36 +msgid "Email Addresses" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:38 +#: kallithea/templates/admin/users/user_edit.html:31 +msgid "API Keys" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:39 +msgid "Owned Repositories" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:40 +#: kallithea/templates/journal/journal.html:53 +msgid "Watched Repositories" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account.html:41 +#: kallithea/templates/admin/permissions/permissions.html:30 +#: kallithea/templates/admin/user_groups/user_group_edit.html:32 +#: kallithea/templates/admin/users/user_edit.html:34 +msgid "Show Permissions" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:6 +#: kallithea/templates/admin/users/user_edit_api_keys.html:6 +msgid "Built-in" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:14 +#: kallithea/templates/admin/users/user_edit_api_keys.html:14 +#, python-format +msgid "Confirm to reset this API key: %s" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:30 +#: kallithea/templates/admin/users/user_edit_api_keys.html:30 +msgid "Expired" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:40 +#: kallithea/templates/admin/users/user_edit_api_keys.html:40 +#, python-format +msgid "Confirm to remove this API key: %s" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:42 +#: kallithea/templates/admin/users/user_edit_api_keys.html:42 +msgid "Remove" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:49 +#: kallithea/templates/admin/users/user_edit_api_keys.html:49 +msgid "No additional API keys specified" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:61 +#: kallithea/templates/admin/users/user_edit_api_keys.html:61 +msgid "New API key" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_api_keys.html:69 +#: kallithea/templates/admin/my_account/my_account_emails.html:45 +#: kallithea/templates/admin/permissions/permissions_ips.html:38 +#: kallithea/templates/admin/repos/repo_add_base.html:81 +#: kallithea/templates/admin/repos/repo_edit_fields.html:58 +#: kallithea/templates/admin/users/user_edit_api_keys.html:69 +#: kallithea/templates/admin/users/user_edit_emails.html:45 +#: kallithea/templates/admin/users/user_edit_ips.html:49 +msgid "Add" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:7 +#: kallithea/templates/admin/users/user_edit_emails.html:7 +msgid "Primary" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:20 +#: kallithea/templates/admin/users/user_edit_emails.html:20 +#, python-format +msgid "Confirm to delete this email: %s" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:26 +#: kallithea/templates/admin/users/user_edit_emails.html:26 +msgid "No additional emails specified." +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_emails.html:38 +#: kallithea/templates/admin/users/user_edit_emails.html:38 +msgid "New email address" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:1 +msgid "Change Your Account Password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:10 +msgid "Current password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:19 +#: kallithea/templates/admin/users/user_edit_profile.html:60 +msgid "New password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:28 +msgid "Confirm new password" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_password.html:45 +#, python-format +msgid "This account is managed with %s and the password cannot be changed here" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:11 +msgid "Change your avatar at" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:12 +#: kallithea/templates/admin/users/user_edit_profile.html:9 +msgid "Using" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:14 +#: kallithea/templates/admin/users/user_edit_profile.html:11 +msgid "Avatars are disabled" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:15 +msgid "Missing email, please update your user email address." +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_profile.html:16 +#: kallithea/templates/admin/users/user_edit_profile.html:15 +msgid "Current IP" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_repos.html:1 +msgid "Repositories You Own" +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_repos.html:59 +#: kallithea/templates/admin/my_account/my_account_watched.html:59 +#: kallithea/templates/base/root.html:45 +#: kallithea/templates/bookmarks/bookmarks.html:81 +#: kallithea/templates/branches/branches.html:81 +#: kallithea/templates/journal/journal.html:200 +#: kallithea/templates/journal/journal.html:291 +#: kallithea/templates/tags/tags.html:81 +msgid "No records found." +msgstr "" + +#: kallithea/templates/admin/my_account/my_account_watched.html:1 +msgid "Repositories You are Watching" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:5 +#: kallithea/templates/admin/notifications/notifications.html:9 +msgid "My Notifications" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:24 +msgid "All" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:25 +msgid "Comments" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:26 +#: kallithea/templates/base/base.html:180 +msgid "Pull Requests" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications.html:30 +msgid "Mark All Read" +msgstr "" + +#: kallithea/templates/admin/notifications/notifications_data.html:40 +msgid "No notifications here yet" +msgstr "" + +#: kallithea/templates/admin/notifications/show_notification.html:5 +#: kallithea/templates/admin/notifications/show_notification.html:11 +msgid "Show Notification" +msgstr "" + +#: kallithea/templates/admin/notifications/show_notification.html:9 +#: kallithea/templates/base/base.html:430 +msgid "Notifications" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions.html:5 +#: kallithea/templates/admin/permissions/permissions.html:11 +#: kallithea/templates/base/base.html:64 +msgid "Default Permissions" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions.html:28 +#: kallithea/templates/admin/settings/settings.html:29 +msgid "Global" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions.html:29 +#: kallithea/templates/admin/users/user_edit.html:32 +msgid "IP Whitelist" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:7 +msgid "Anonymous access" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:13 +#, python-format +msgid "Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:25 +msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:26 +msgid "Apply to all existing repositories" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:27 +msgid "Permissions for the Default user on new repositories." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:32 +#: kallithea/templates/admin/repos/repo_add_base.html:37 +#: kallithea/templates/admin/repos/repo_edit_settings.html:35 +#: kallithea/templates/data_table/_dt_elements.html:202 +#: kallithea/templates/forks/fork.html:48 +msgid "Repository group" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:39 +msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:40 +msgid "Apply to all existing repository groups" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:41 +msgid "Permissions for the Default user on new repository groups." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:46 +#: kallithea/templates/data_table/_dt_elements.html:209 +msgid "User group" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:53 +msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:54 +msgid "Apply to all existing user groups" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:55 +msgid "Permissions for the Default user on new user groups." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:60 +msgid "Top level repository creation" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:64 +msgid "Enable this to allow non-admins to create repositories at the top level." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:65 +msgid "Note: This will also give all users API access to create repositories everywhere. That might change in future versions." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:70 +msgid "Repository creation with group write access" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:74 +msgid "With this, write permission to a repository group allows creating repositories inside that group. Without this, group write permissions mean nothing." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:79 +msgid "User group creation" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:83 +msgid "Enable this to allow non-admins to create user groups." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:88 +msgid "Repository forking" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:92 +msgid "Enable this to allow non-admins to fork repositories." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:97 +msgid "Registration" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_globals.html:105 +msgid "External auth account activation" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_ips.html:13 +#: kallithea/templates/admin/users/user_edit_ips.html:23 +#, python-format +msgid "Confirm to delete this IP address: %s" +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_ips.html:19 +#: kallithea/templates/admin/users/user_edit_ips.html:30 +msgid "All IP addresses are allowed." +msgstr "" + +#: kallithea/templates/admin/permissions/permissions_ips.html:30 +#: kallithea/templates/admin/users/user_edit_ips.html:42 +msgid "New IP address" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:11 +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:11 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105 +#: kallithea/templates/admin/repo_groups/repo_groups.html:10 +#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80 +msgid "Repository Groups" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:33 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8 +#: kallithea/templates/admin/user_groups/user_group_add.html:32 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7 +msgid "Group name" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:51 +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26 +msgid "Group parent" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:60 +#: kallithea/templates/admin/repos/repo_add_base.html:46 +msgid "Copy parent group permissions" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_add.html:64 +#: kallithea/templates/admin/repos/repo_add_base.html:50 +msgid "Copy permission set from parent repository group." +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:5 +#, python-format +msgid "%s Repository Group Settings" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21 +msgid "Add Child Group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40 +#: kallithea/templates/admin/repos/repo_edit.html:12 +#: kallithea/templates/admin/repos/repo_edit.html:40 +#: kallithea/templates/admin/settings/settings.html:11 +#: kallithea/templates/admin/user_groups/user_group_edit.html:29 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 +#: kallithea/templates/data_table/_dt_elements.html:45 +#: kallithea/templates/data_table/_dt_elements.html:49 +msgid "Settings" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41 +#: kallithea/templates/admin/repos/repo_edit.html:46 +#: kallithea/templates/admin/user_groups/user_group_edit.html:30 +#: kallithea/templates/admin/users/user_edit.html:33 +msgid "Advanced" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42 +#: kallithea/templates/admin/repos/repo_edit.html:43 +#: kallithea/templates/admin/user_groups/user_group_edit.html:31 +msgid "Permissions" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:1 +#, python-format +msgid "Repository Group: %s" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:6 +msgid "Top level repositories" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7 +msgid "Total repositories" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8 +msgid "Children groups" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9 +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7 +#: kallithea/templates/admin/users/user_edit_advanced.html:8 +#: kallithea/templates/pullrequests/pullrequest_show.html:148 +msgid "Created on" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21 +#: kallithea/templates/data_table/_dt_elements.html:190 +#, python-format +msgid "Confirm to delete this group: %s with %s repository" +msgid_plural "Confirm to delete this group: %s with %s repositories" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25 +msgid "Delete this repository group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:12 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11 +msgid "User/User Group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:24 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:37 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45 +msgid "Default" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34 +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:43 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:68 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71 +msgid "Revoke" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97 +#: kallithea/templates/admin/repos/repo_edit_permissions.html:94 +#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97 +msgid "Add new" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103 +msgid "Apply to children" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107 +msgid "Both" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108 +msgid "Set or revoke permission to all children of that group, including non-private repositories and other groups if selected." +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38 +msgid "Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53 +msgid "Remove this group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53 +msgid "Confirm to delete this group" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_show.html:4 +#, python-format +msgid "%s Repository group dashboard" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_show.html:9 +msgid "Home" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_group_show.html:13 +msgid "with" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_groups.html:5 +msgid "Repository Groups Administration" +msgstr "" + +#: kallithea/templates/admin/repo_groups/repo_groups.html:48 +msgid "Number of Top-level Repositories" +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:17 +msgid "Clone remote repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:22 +msgid "Optional: URL of a remote repository. If set, the repository will be created as a clone from this URL." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:32 +#: kallithea/templates/admin/repos/repo_edit_settings.html:69 +#: kallithea/templates/forks/fork.html:42 +msgid "Keep it short and to the point. Use a README file for longer descriptions." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:41 +#: kallithea/templates/admin/repos/repo_edit_settings.html:39 +#: kallithea/templates/forks/fork.html:52 +msgid "Optionally select a group to put this repository into." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:59 +msgid "Type of repository to create." +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:64 +#: kallithea/templates/admin/repos/repo_edit_settings.html:44 +#: kallithea/templates/forks/fork.html:58 +msgid "Landing revision" +msgstr "" + +#: kallithea/templates/admin/repos/repo_add_base.html:68 +msgid "Default revision for files page, downloads, full text search index and readme generation" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:9 +#, python-format +msgid "%s Creating Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:13 +msgid "Creating repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:27 +#, python-format +msgid "Repository \"%(repo_name)s\" is being created, you will be redirected when this process is finished.repo_name" +msgstr "" + +#: kallithea/templates/admin/repos/repo_creating.html:39 +msgid "We're sorry but error occurred during this operation. Please check your Kallithea server logs, or contact administrator." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:8 +#, python-format +msgid "%s Repository Settings" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:49 +msgid "Extra Fields" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:52 +msgid "Caches" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:55 +msgid "Remote" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit.html:58 +#: kallithea/templates/summary/statistics.html:8 +#: kallithea/templates/summary/summary.html:171 +#: kallithea/templates/summary/summary.html:172 +msgid "Statistics" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:1 +msgid "Parent" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:5 +#: kallithea/templates/admin/repos/repo_edit_fork.html:5 +msgid "Set" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:8 +#: kallithea/templates/admin/repos/repo_edit_fork.html:9 +msgid "Manually set this repository as a fork of another from the list." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:22 +msgid "Public Journal Visibility" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:29 +msgid "Remove from public journal" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:34 +msgid "Add to Public Journal" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:40 +msgid "All actions done in this repository will be visible to everyone in the public journal." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:46 +msgid "Change Locking" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:52 +msgid "Confirm to unlock repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:54 +msgid "Unlock Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:60 +msgid "Confirm to lock repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:62 +msgid "Lock Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:64 +msgid "Repository is not locked" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:68 +msgid "Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository. The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:79 +#: kallithea/templates/data_table/_dt_elements.html:130 +#, python-format +msgid "Confirm to delete this repository: %s" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:81 +msgid "Delete this Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:84 +#, python-format +msgid "This repository has %s fork" +msgid_plural "This repository has %s forks" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:85 +msgid "Detach forks" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:86 +msgid "Delete forks" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_advanced.html:90 +msgid "The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:4 +msgid "Invalidate Repository Cache" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:7 +msgid "Manually invalidate cache for this repository. On first access, the repository will be cached again." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:12 +msgid "List of Cached Values" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:15 +msgid "Prefix" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:16 +#: kallithea/templates/admin/repos/repo_edit_fields.html:6 +msgid "Key" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_caches.html:17 +#: kallithea/templates/admin/user_groups/user_group_add.html:49 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24 +#: kallithea/templates/admin/user_groups/user_groups.html:49 +#: kallithea/templates/admin/users/user_add.html:86 +#: kallithea/templates/admin/users/user_edit_profile.html:96 +#: kallithea/templates/admin/users/users.html:54 +msgid "Active" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:5 +msgid "Label" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:19 +#, python-format +msgid "Confirm to delete this field: %s" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:33 +msgid "New field key" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:41 +msgid "New field label" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:44 +msgid "Enter short label" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:50 +msgid "New field description" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:53 +msgid "Enter description of a field" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_fields.html:66 +msgid "Extra fields are disabled." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_permissions.html:21 +msgid "Private Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:3 +msgid "Remote repository URL" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:9 +msgid "Pull Changes from Remote Repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:11 +msgid "Confirm to pull changes from remote repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_remote.html:17 +msgid "This repository does not have a remote repository URL." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:11 +msgid "Permanent Repository ID" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:11 +msgid "What is that?" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:13 +msgid "URL by id" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:14 +msgid "" +"In case this repository is renamed or moved into another group the repository URL changes.\n" +" Using the above permanent URL guarantees that this repository always will be accessible on that URL.\n" +" This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:21 +msgid "Remote repository" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:25 +msgid "Repository URL" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:29 +msgid "Optional: URL of a remote repository. If set, the repository can be pulled from this URL." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:48 +msgid "Default revision for files page, downloads, whoosh and readme" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_settings.html:58 +msgid "Change owner of this repository." +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:6 +msgid "Processed commits" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:7 +msgid "Processed progress" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:10 +msgid "Reset Statistics" +msgstr "" + +#: kallithea/templates/admin/repos/repo_edit_statistics.html:10 +msgid "Confirm to remove current statistics." +msgstr "" + +#: kallithea/templates/admin/repos/repos.html:5 +msgid "Repositories Administration" +msgstr "" + +#: kallithea/templates/admin/repos/repos.html:51 +msgid "State" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:5 +msgid "Settings Administration" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:27 +msgid "VCS" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:28 +msgid "Remap and Rescan" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:30 +msgid "Visual" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:32 +#: kallithea/templates/admin/settings/settings_vcs.html:19 +msgid "Hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:33 +msgid "Full Text Search" +msgstr "" + +#: kallithea/templates/admin/settings/settings.html:34 +msgid "System Info" +msgstr "" + +#: kallithea/templates/admin/settings/settings_email.html:7 +msgid "Send test email to" +msgstr "" + +#: kallithea/templates/admin/settings/settings_email.html:15 +msgid "Send" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:8 +msgid "Site branding" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:12 +msgid "Set a custom title for your Kallithea Service." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:18 +msgid "HTTP authentication realm" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:27 +msgid "Analytics HTML block" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:31 +msgid "HTML with JavaScript for web analytics systems like Google Analytics or Piwik. This will be added at the bottom of every page." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:37 +msgid "ReCaptcha public key" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:41 +msgid "Public key for reCaptcha system." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:47 +msgid "ReCaptcha private key" +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:51 +msgid "Private key for reCaptcha system. Setting this value will enable captcha on registration." +msgstr "" + +#: kallithea/templates/admin/settings/settings_global.html:56 +#: kallithea/templates/admin/settings/settings_vcs.html:80 +#: kallithea/templates/admin/settings/settings_visual.html:116 +msgid "Save Settings" +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:1 +msgid "Built-in Mercurial Hooks (Read-Only)" +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:15 +msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications." +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:19 +msgid "Custom Hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings_hooks.html:67 +msgid "Failed to remove hook" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:6 +msgid "Rescan option" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:11 +msgid "Delete records of missing repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:13 +msgid "Check this option to remove all comments, pull requests and other records related to repositories that no longer exist in the filesystem." +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:17 +msgid "Invalidate cache for all repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:19 +msgid "Check this to reload data and clear cache keys for all repositories." +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:23 +msgid "Install Git hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:25 +msgid "Verify if Kallithea's Git hooks are installed for each repository. Current hooks will be updated to the latest version." +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:28 +msgid "Overwrite existing Git hooks" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:30 +msgid "If installing Git hooks, overwrite any existing hooks, even if they do not seem to come from Kallithea. WARNING: This operation will destroy any custom git hooks you may have deployed by hand!" +msgstr "" + +#: kallithea/templates/admin/settings/settings_mapping.html:35 +msgid "Rescan Repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:7 +msgid "Index build option" +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:12 +msgid "Build from scratch" +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:15 +msgid "This option completely reindexeses all of the repositories for proper fulltext search capabilities." +msgstr "" + +#: kallithea/templates/admin/settings/settings_search.html:21 +msgid "Reindex" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:4 +msgid "Kallithea version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:4 +msgid "Check for updates" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:5 +msgid "Kallithea configuration file" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:6 +msgid "Python version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:7 +msgid "Platform" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:8 +msgid "Git version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:9 +msgid "Git path" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:10 +msgid "Upgrade info endpoint" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:10 +msgid "Note: please make sure this server can access this URL" +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:15 +msgid "Checking for updates..." +msgstr "" + +#: kallithea/templates/admin/settings/settings_system.html:23 +msgid "Python Packages" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:6 +msgid "Web" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:11 +msgid "Require SSL for vcs operations" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:13 +msgid "Activate to require SSL both pushing and pulling. If SSL certificate is missing, it will return an HTTP Error 406: Not Acceptable." +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:24 +msgid "Show repository size after push" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:28 +msgid "Log user push commands" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:32 +msgid "Log user pull commands" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:36 +msgid "Update repository after push (hg update)" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:42 +msgid "Mercurial extensions" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:47 +msgid "Enable largefiles extension" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:51 +msgid "Enable hgsubversion extension" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:53 +msgid "Requires hgsubversion library to be installed. Enables cloning of remote Subversion repositories while converting them to Mercurial." +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:64 +msgid "Location of repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:69 +msgid "Click to unlock. You must restart Kallithea in order to make this setting take effect." +msgstr "" + +#: kallithea/templates/admin/settings/settings_vcs.html:72 +msgid "Filesystem location where repositories are stored. After changing this value, a restart and rescan of the repository folder are both required." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:8 +msgid "General" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:13 +msgid "Use repository extra fields" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:15 +msgid "Allows storing additional customized fields per repository." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:18 +msgid "Show Kallithea version" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:20 +msgid "Shows or hides a version number of Kallithea displayed in the footer." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:24 +msgid "Use Gravatars in Kallithea" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:30 +msgid "" +"Gravatar URL allows you to use another avatar server application.\n" +" The following variables of the URL will be replaced accordingly.\n" +" {scheme} 'http' or 'https' sent from running Kallithea server,\n" +" {email} user email,\n" +" {md5email} md5 hash of the user email (like at gravatar.com),\n" +" {size} size of the image that is expected from the server application,\n" +" {netloc} network location/server host of running Kallithea server" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:42 +msgid "" +"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.\n" +" The following variables are available:\n" +" {scheme} 'http' or 'https' sent from running Kallithea server,\n" +" {user} current user username,\n" +" {netloc} network location/server host of running Kallithea server,\n" +" {repo} full repository name,\n" +" {repoid} ID of repository, can be used to contruct clone-by-id" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:55 +msgid "Dashboard items" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:59 +msgid "Number of items displayed in the main page dashboard before pagination is shown." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:65 +msgid "Admin pages items" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:69 +msgid "Number of items displayed in the admin pages grids before pagination is shown." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:75 +msgid "Icons" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:80 +msgid "Show public repository icon on repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:84 +msgid "Show private repository icon on repositories" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:86 +msgid "Show public/private icons next to repository names." +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:92 +msgid "Meta Tagging" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:97 +msgid "Stylify recognised meta tags:" +msgstr "" + +#: kallithea/templates/admin/settings/settings_visual.html:111 +msgid "Parses meta tags from the repository description field and turns them into colored tags." +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:5 +msgid "Add user group" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:10 +#: kallithea/templates/admin/user_groups/user_group_edit.html:11 +#: kallithea/templates/admin/user_groups/user_groups.html:10 +#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83 +msgid "User Groups" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:12 +#: kallithea/templates/admin/user_groups/user_groups.html:25 +msgid "Add User Group" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_add.html:44 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19 +msgid "Short, optional description for this user group." +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit.html:5 +#, python-format +msgid "%s user group settings" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit.html:33 +msgid "Show Members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1 +#, python-format +msgid "User Group: %s" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6 +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32 +#: kallithea/templates/admin/user_groups/user_groups.html:48 +msgid "Members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19 +#: kallithea/templates/data_table/_dt_elements.html:174 +#, python-format +msgid "Confirm to delete this user group: %s" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21 +msgid "Delete this user group" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17 +msgid "No members yet" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40 +msgid "Chosen group members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49 +msgid "Available members" +msgstr "" + +#: kallithea/templates/admin/user_groups/user_groups.html:5 +msgid "User Groups Administration" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:5 +msgid "Add user" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:10 +#: kallithea/templates/admin/users/user_edit.html:11 +#: kallithea/templates/admin/users/users.html:10 +#: kallithea/templates/base/base.html:62 +msgid "Users" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:12 +#: kallithea/templates/admin/users/users.html:24 +msgid "Add User" +msgstr "" + +#: kallithea/templates/admin/users/user_add.html:50 +msgid "Password confirmation" +msgstr "" + +#: kallithea/templates/admin/users/user_edit.html:5 +#, python-format +msgid "%s user settings" +msgstr "" + +#: kallithea/templates/admin/users/user_edit.html:30 +msgid "Emails" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:1 +#, python-format +msgid "User: %s" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:7 +#: kallithea/templates/admin/users/user_edit_profile.html:42 +msgid "Source of Record" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:9 +#: kallithea/templates/admin/users/users.html:53 +msgid "Last Login" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:10 +msgid "Member of User Groups" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:21 +#: kallithea/templates/data_table/_dt_elements.html:158 +#, python-format +msgid "Confirm to delete this user: %s" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_advanced.html:23 +msgid "Delete this user" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_ips.html:8 +#, python-format +msgid "Inherited from %s" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:8 +msgid "Change avatar at" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:12 +msgid "Missing email, please update this user email address." +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:51 +msgid "Name in Source of Record" +msgstr "" + +#: kallithea/templates/admin/users/user_edit_profile.html:69 +msgid "New password confirmation" +msgstr "" + +#: kallithea/templates/admin/users/users.html:5 +msgid "Users Administration" +msgstr "" + +#: kallithea/templates/admin/users/users.html:56 +msgid "Auth Type" +msgstr "" + +#: kallithea/templates/base/base.html:18 +#, python-format +msgid "Server instance: %s" +msgstr "" + +#: kallithea/templates/base/base.html:30 +msgid "Support" +msgstr "" + +#: kallithea/templates/base/base.html:90 +msgid "Mercurial repository" +msgstr "" + +#: kallithea/templates/base/base.html:93 +msgid "Git repository" +msgstr "" + +#: kallithea/templates/base/base.html:119 +msgid "Create Fork" +msgstr "" + +#: kallithea/templates/base/base.html:130 +#: kallithea/templates/data_table/_dt_elements.html:13 +#: kallithea/templates/data_table/_dt_elements.html:17 +#: kallithea/templates/summary/summary.html:8 +msgid "Summary" +msgstr "" + +#: kallithea/templates/base/base.html:132 +#: kallithea/templates/base/base.html:134 +#: kallithea/templates/changelog/changelog.html:14 +#: kallithea/templates/data_table/_dt_elements.html:21 +#: kallithea/templates/data_table/_dt_elements.html:25 +msgid "Changelog" +msgstr "" + +#: kallithea/templates/base/base.html:136 +#: kallithea/templates/data_table/_dt_elements.html:29 +#: kallithea/templates/data_table/_dt_elements.html:33 +#: kallithea/templates/files/files.html:11 +msgid "Files" +msgstr "" + +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 +msgid "Options" +msgstr "" + +#: kallithea/templates/base/base.html:152 +#: kallithea/templates/forks/forks_data.html:21 +msgid "Compare Fork" +msgstr "" + +#: kallithea/templates/base/base.html:154 +#: kallithea/templates/bookmarks/bookmarks.html:56 +#: kallithea/templates/bookmarks/bookmarks_data.html:13 +#: kallithea/templates/branches/branches.html:56 +#: kallithea/templates/branches/branches_data.html:13 +#: kallithea/templates/tags/tags.html:56 +#: kallithea/templates/tags/tags_data.html:13 +msgid "Compare" +msgstr "" + +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 +#: kallithea/templates/search/search.html:14 +#: kallithea/templates/search/search.html:54 +msgid "Search" +msgstr "" + +#: kallithea/templates/base/base.html:160 +msgid "Unlock" +msgstr "" + +#: kallithea/templates/base/base.html:162 +msgid "Lock" +msgstr "" + +#: kallithea/templates/base/base.html:170 +msgid "Follow" +msgstr "" + +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + +#: kallithea/templates/base/base.html:174 +#: kallithea/templates/data_table/_dt_elements.html:37 +#: kallithea/templates/data_table/_dt_elements.html:41 +#: kallithea/templates/forks/fork.html:9 +msgid "Fork" +msgstr "" + +#: kallithea/templates/base/base.html:175 +#: kallithea/templates/pullrequests/pullrequest.html:88 +msgid "Create Pull Request" +msgstr "" + +#: kallithea/templates/base/base.html:180 +#, python-format +msgid "Show Pull Requests for %s" +msgstr "" + +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 +msgid "Show recent activity" +msgstr "" + +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 +msgid "Public journal" +msgstr "" + +#: kallithea/templates/base/base.html:317 +msgid "Show public gists" +msgstr "" + +#: kallithea/templates/base/base.html:318 +msgid "Gists" +msgstr "" + +#: kallithea/templates/base/base.html:322 +msgid "All Public Gists" +msgstr "" + +#: kallithea/templates/base/base.html:324 +msgid "My Public Gists" +msgstr "" + +#: kallithea/templates/base/base.html:325 +msgid "My Private Gists" +msgstr "" + +#: kallithea/templates/base/base.html:330 +msgid "Search in repositories" +msgstr "" + +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:6 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:10 +msgid "My Pull Requests" +msgstr "" + +#: kallithea/templates/base/base.html:377 +msgid "Not Logged In" +msgstr "" + +#: kallithea/templates/base/base.html:384 +msgid "Login to Your Account" +msgstr "" + +#: kallithea/templates/base/base.html:407 +msgid "Forgot password ?" +msgstr "" + +#: kallithea/templates/base/base.html:434 +msgid "Log Out" +msgstr "" + +#: kallithea/templates/base/base.html:615 +msgid "Keyboard shortcuts" +msgstr "" + +#: kallithea/templates/base/base.html:624 +msgid "Site-wide shortcuts" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:14 +msgid "Inherit defaults" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:19 +#, python-format +msgid "Select to inherit global settings, IP whitelist and permissions from the %s." +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:28 +msgid "Create repositories" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:33 +msgid "Select this option to allow repository creation for this user" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:40 +msgid "Create user groups" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:45 +msgid "Select this option to allow user group creation for this user" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:52 +msgid "Fork repositories" +msgstr "" + +#: kallithea/templates/base/default_perms_box.html:57 +msgid "Select this option to allow repository forking for this user" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:13 +#: kallithea/templates/changelog/changelog.html:42 +msgid "Show" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:22 +msgid "No permissions defined yet" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:30 +#: kallithea/templates/base/perms_summary.html:54 +msgid "Permission" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:32 +#: kallithea/templates/base/perms_summary.html:56 +msgid "Edit Permission" +msgstr "" + +#: kallithea/templates/base/perms_summary.html:90 +msgid "No permission defined" +msgstr "" + +#: kallithea/templates/base/root.html:22 +msgid "Add Another Comment" +msgstr "" + +#: kallithea/templates/base/root.html:23 +#: kallithea/templates/data_table/_dt_elements.html:214 +msgid "Stop following this repository" +msgstr "" + +#: kallithea/templates/base/root.html:24 +msgid "Start following this repository" +msgstr "" + +#: kallithea/templates/base/root.html:25 +msgid "Group" +msgstr "" + +#: kallithea/templates/base/root.html:26 +msgid "members" +msgstr "" + +#: kallithea/templates/base/root.html:27 +msgid "Loading ..." +msgstr "" + +#: kallithea/templates/base/root.html:28 +msgid "loading ..." +msgstr "" + +#: kallithea/templates/base/root.html:29 +msgid "Search truncated" +msgstr "" + +#: kallithea/templates/base/root.html:30 +msgid "No matching files" +msgstr "" + +#: kallithea/templates/base/root.html:31 +msgid "Open New Pull Request from {0}" +msgstr "" + +#: kallithea/templates/base/root.html:32 +msgid "Open New Pull Request for {0} → {1}" +msgstr "" + +#: kallithea/templates/base/root.html:33 +msgid "Show Selected Changesets {0} → {1}" +msgstr "" + +#: kallithea/templates/base/root.html:34 +msgid "Selection Link" +msgstr "" + +#: kallithea/templates/base/root.html:35 +#: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 +msgid "Collapse Diff" +msgstr "" + +#: kallithea/templates/base/root.html:36 +msgid "Expand Diff" +msgstr "" + +#: kallithea/templates/base/root.html:37 +msgid "Failed to revoke permission" +msgstr "" + +#: kallithea/templates/base/root.html:38 +msgid "Confirm to revoke permission for {0}: {1} ?" +msgstr "" + +#: kallithea/templates/base/root.html:39 +msgid "enabled" +msgstr "" + +#: kallithea/templates/base/root.html:40 +msgid "disabled" +msgstr "" + +#: kallithea/templates/base/root.html:42 +msgid "Specify changeset" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:5 +#, python-format +msgid "%s Bookmarks" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:26 +msgid "Compare Bookmarks" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:53 +#: kallithea/templates/bookmarks/bookmarks_data.html:10 +#: kallithea/templates/branches/branches.html:53 +#: kallithea/templates/branches/branches_data.html:10 +#: kallithea/templates/changelog/changelog_summary_data.html:10 +#: kallithea/templates/tags/tags.html:53 +#: kallithea/templates/tags/tags_data.html:10 +msgid "Author" +msgstr "" + +#: kallithea/templates/bookmarks/bookmarks.html:54 +#: kallithea/templates/bookmarks/bookmarks_data.html:12 +#: kallithea/templates/branches/branches.html:54 +#: kallithea/templates/branches/branches_data.html:12 +#: kallithea/templates/changelog/changelog_summary_data.html:7 +#: kallithea/templates/files/files_browser.html:32 +#: kallithea/templates/pullrequests/pullrequest.html:62 +#: kallithea/templates/pullrequests/pullrequest.html:78 +#: kallithea/templates/tags/tags.html:54 +#: kallithea/templates/tags/tags_data.html:12 +msgid "Revision" +msgstr "" + +#: kallithea/templates/branches/branches.html:5 +#, python-format +msgid "%s Branches" +msgstr "" + +#: kallithea/templates/branches/branches.html:26 +msgid "Compare Branches" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:6 +#, python-format +msgid "%s Changelog" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:21 +#, python-format +msgid "showing %d out of %d revision" +msgid_plural "showing %d out of %d revisions" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changelog/changelog.html:49 +msgid "Clear selection" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:55 +msgid "Go to tip of repository" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:60 +#: kallithea/templates/forks/forks_data.html:19 +#, python-format +msgid "Compare fork with %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:62 +#, python-format +msgid "Compare fork with parent repository (%s)" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:66 +#: kallithea/templates/files/files.html:29 +msgid "Branch filter:" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:92 +#: kallithea/templates/changelog/changelog_summary_data.html:20 +#, python-format +msgid "" +"Changeset status: %s\n" +"Click to open associated pull request %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:96 +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/compare/compare_cs.html:63 +msgid "Expand commit message" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/compare/compare_cs.html:30 +msgid "Changeset has comments" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:134 +#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changeset/changeset.html:94 +#: kallithea/templates/changeset/changeset_range.html:92 +#, python-format +msgid "Bookmark %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:140 +#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changeset/changeset.html:101 +#: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 +#, python-format +msgid "Tag %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:145 +#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changeset/changeset.html:106 +#: kallithea/templates/changeset/changeset_range.html:102 +#, python-format +msgid "Branch %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:310 +msgid "There are no changes yet" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:4 +#: kallithea/templates/changeset/changeset.html:77 +msgid "Removed" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:5 +#: kallithea/templates/changeset/changeset.html:78 +msgid "Changed" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:6 +#: kallithea/templates/changeset/changeset.html:79 +#: kallithea/templates/changeset/diff_block.html:47 +msgid "Added" +msgstr "" + +#: kallithea/templates/changelog/changelog_details.html:8 +#: kallithea/templates/changelog/changelog_details.html:9 +#: kallithea/templates/changelog/changelog_details.html:10 +#: kallithea/templates/changeset/changeset.html:81 +#: kallithea/templates/changeset/changeset.html:82 +#: kallithea/templates/changeset/changeset.html:83 +#, python-format +msgid "Affected %s files" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:8 +#: kallithea/templates/files/files_add.html:60 +#: kallithea/templates/files/files_delete.html:39 +#: kallithea/templates/files/files_edit.html:63 +msgid "Commit Message" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:9 +#: kallithea/templates/pullrequests/pullrequest_data.html:17 +msgid "Age" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:11 +msgid "Refs" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:81 +msgid "Add or upload files directly via Kallithea" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/files/files_add.html:21 +#: kallithea/templates/files/files_ypjax.html:9 +msgid "Add New File" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:90 +msgid "Push new repository" +msgstr "" + +#: kallithea/templates/changelog/changelog_summary_data.html:98 +msgid "Existing repository?" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:8 +#, python-format +msgid "%s Changeset" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:36 +msgid "Parent rev." +msgstr "" + +#: kallithea/templates/changeset/changeset.html:42 +msgid "Child rev." +msgstr "" + +#: kallithea/templates/changeset/changeset.html:50 +#: kallithea/templates/changeset/changeset_file_comment.html:39 +#: kallithea/templates/changeset/changeset_range.html:48 +msgid "Changeset status" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:54 +#: kallithea/templates/changeset/diff_block.html:72 +#: kallithea/templates/files/diff_2way.html:49 +msgid "Raw diff" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:57 +msgid "Patch diff" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:60 +#: kallithea/templates/changeset/diff_block.html:75 +#: kallithea/templates/files/diff_2way.html:52 +msgid "Download diff" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:89 +#: kallithea/templates/changeset/changeset_range.html:88 +msgid "Merge" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:123 +msgid "Grafted from:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:129 +msgid "Transplanted from:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:135 +msgid "Replaced by:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:149 +msgid "Preceded by:" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:166 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 +#, python-format +msgid "%s file changed" +msgid_plural "%s files changed" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset.html:168 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 +#, python-format +msgid "%s file changed with %s insertions and %s deletions" +msgid_plural "%s files changed with %s insertions and %s deletions" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset.html:182 +#: kallithea/templates/changeset/changeset.html:195 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 +msgid "Show full diff anyway" +msgstr "" + +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 +msgid "No revisions" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:21 +msgid "on pull request" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:22 +msgid "No title" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:24 +msgid "on this changeset" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:31 +msgid "Delete comment?" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:39 +msgid "Status change" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:59 +msgid "Commenting on line." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:60 +msgid "Comments are in plain text. Use @username inside this text to notify another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +msgid "Finish pull request" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 +msgid "Submitting ..." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:104 +msgid "Comment" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:112 +msgid "You need to be logged in to comment." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:112 +msgid "Login now" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:116 +msgid "Hide" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:128 +#, python-format +msgid "%d comment" +msgid_plural "%d comments" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset_file_comment.html:129 +#, python-format +msgid "%d inline" +msgid_plural "%d inline" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset_file_comment.html:130 +#, python-format +msgid "%d general" +msgid_plural "%d general" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/changeset/changeset_range.html:5 +#, python-format +msgid "%s Changesets" +msgstr "" + +#: kallithea/templates/changeset/changeset_range.html:56 +msgid "Files affected" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:66 +#: kallithea/templates/files/diff_2way.html:43 +msgid "Show full diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:69 +#: kallithea/templates/files/diff_2way.html:46 +msgid "Show full side-by-side diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:83 +msgid "Show inline comments" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:4 +msgid "No changesets" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:8 +msgid "Ancestor" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:44 +msgid "First (oldest) changeset in this list" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:46 +msgid "Last (most recent) changeset in this list" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:48 +msgid "Position in this list of changesets" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:85 +msgid "Show merge diff" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 +msgid "Common ancestor" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 +#, python-format +msgid "%s changesets" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:109 +msgid "behind" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:6 +#: kallithea/templates/compare/compare_diff.html:8 +#, python-format +msgid "%s Compare" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:13 +#: kallithea/templates/compare/compare_diff.html:41 +msgid "Compare Revisions" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:39 +msgid "Swap" +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:48 +msgid "Compare revisions, branches, bookmarks, or tags." +msgstr "" + +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 +#, python-format +msgid "Showing %s commit" +msgid_plural "Showing %s commits" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +msgid "Show full diff" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:74 +msgid "Public repository" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:84 +msgid "Repository creation in progress..." +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:98 +msgid "No changesets yet" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:105 +#: kallithea/templates/data_table/_dt_elements.html:107 +#, python-format +msgid "Subscribe to %s rss feed" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:113 +#: kallithea/templates/data_table/_dt_elements.html:115 +#, python-format +msgid "Subscribe to %s atom feed" +msgstr "" + +#: kallithea/templates/data_table/_dt_elements.html:139 +msgid "Creating" +msgstr "" + +#: kallithea/templates/email_templates/changeset_comment.html:5 +#, python-format +msgid "Comment from %s on %s changeset %s mentioned you" +msgstr "" + +#: kallithea/templates/email_templates/changeset_comment.html:7 +#, python-format +msgid "Comment from %s on %s changeset %s" +msgstr "" + +#: kallithea/templates/email_templates/changeset_comment.html:12 +msgid "The changeset status was changed to" +msgstr "" + +#: kallithea/templates/email_templates/main.html:6 +msgid "This is an automatic notification. Don't reply to this mail." +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:4 +#, python-format +msgid "Hello %s" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:6 +msgid "We have received a request to reset the password for your account." +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "This account is however managed outside this system and the password cannot be changed here." +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 +msgid "Should you not be able to use the link above, please type the following code into the password reset form" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:16 +msgid "If it weren't you who requested the password reset, just disregard this message." +msgstr "" + +#: kallithea/templates/email_templates/pull_request.html:5 +#, python-format +msgid "%s mentioned you on %s pull request \"%s\"" +msgstr "" + +#: kallithea/templates/email_templates/pull_request.html:7 +#, python-format +msgid "%s requested your review of %s pull request \"%s\"" +msgstr "" + +#: kallithea/templates/email_templates/pull_request_comment.html:4 +#, python-format +msgid "Comment from %s on %s pull request \"%s\"" +msgstr "" + +#: kallithea/templates/email_templates/pull_request_comment.html:9 +msgid "The comment closed the pull request with status" +msgstr "" + +#: kallithea/templates/email_templates/pull_request_comment.html:11 +msgid "The comment was made with status" +msgstr "" + +#: kallithea/templates/email_templates/registration.html:6 +msgid "View this user here" +msgstr "" + +#: kallithea/templates/files/diff_2way.html:15 +#, python-format +msgid "%s File side-by-side diff" +msgstr "" + +#: kallithea/templates/files/diff_2way.html:19 +#: kallithea/templates/files/file_diff.html:8 +msgid "File diff" +msgstr "" + +#: kallithea/templates/files/file_diff.html:4 +#, python-format +msgid "%s File Diff" +msgstr "" + +#: kallithea/templates/files/files.html:4 +#: kallithea/templates/files/files.html:80 +#, python-format +msgid "%s Files" +msgstr "" + +#: kallithea/templates/files/files_add.html:4 +#, python-format +msgid "%s Files Add" +msgstr "" + +#: kallithea/templates/files/files_add.html:40 +#: kallithea/templates/files/files_edit.html:38 +#: kallithea/templates/files/files_ypjax.html:3 +msgid "Location" +msgstr "" + +#: kallithea/templates/files/files_add.html:42 +msgid "Enter filename..." +msgstr "" + +#: kallithea/templates/files/files_add.html:44 +#: kallithea/templates/files/files_add.html:48 +msgid "or" +msgstr "" + +#: kallithea/templates/files/files_add.html:44 +msgid "Upload File" +msgstr "" + +#: kallithea/templates/files/files_add.html:48 +msgid "Create New File" +msgstr "" + +#: kallithea/templates/files/files_add.html:53 +msgid "New file type" +msgstr "" + +#: kallithea/templates/files/files_add.html:64 +#: kallithea/templates/files/files_delete.html:43 +#: kallithea/templates/files/files_edit.html:67 +msgid "Commit Changes" +msgstr "" + +#: kallithea/templates/files/files_browser.html:33 +msgid "Previous revision" +msgstr "" + +#: kallithea/templates/files/files_browser.html:35 +msgid "Next revision" +msgstr "" + +#: kallithea/templates/files/files_browser.html:41 +msgid "Follow current branch" +msgstr "" + +#: kallithea/templates/files/files_browser.html:44 +msgid "Search File List" +msgstr "" + +#: kallithea/templates/files/files_browser.html:48 +msgid "Loading file list..." +msgstr "" + +#: kallithea/templates/files/files_browser.html:61 +msgid "Size" +msgstr "" + +#: kallithea/templates/files/files_browser.html:62 +msgid "Last Revision" +msgstr "" + +#: kallithea/templates/files/files_browser.html:63 +msgid "Last Modified" +msgstr "" + +#: kallithea/templates/files/files_browser.html:64 +msgid "Last Committer" +msgstr "" + +#: kallithea/templates/files/files_delete.html:4 +#, python-format +msgid "%s Files Delete" +msgstr "" + +#: kallithea/templates/files/files_delete.html:12 +#: kallithea/templates/files/files_delete.html:31 +msgid "Delete file" +msgstr "" + +#: kallithea/templates/files/files_edit.html:4 +#, python-format +msgid "%s File Edit" +msgstr "" + +#: kallithea/templates/files/files_edit.html:21 +msgid "Edit file" +msgstr "" + +#: kallithea/templates/files/files_edit.html:48 +#: kallithea/templates/files/files_source.html:32 +msgid "Show Annotation" +msgstr "" + +#: kallithea/templates/files/files_edit.html:50 +#: kallithea/templates/files/files_source.html:35 +msgid "Download as Raw" +msgstr "" + +#: kallithea/templates/files/files_edit.html:53 +msgid "Source" +msgstr "" + +#: kallithea/templates/files/files_edit.html:58 +msgid "Editing file" +msgstr "" + +#: kallithea/templates/files/files_history_box.html:2 +#, python-format +msgid "%s author" +msgid_plural "%s authors" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/files/files_source.html:7 +msgid "Diff to Revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:8 +msgid "Show at Revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:10 +msgid "Show Full History" +msgstr "" + +#: kallithea/templates/files/files_source.html:11 +msgid "Show Authors" +msgstr "" + +#: kallithea/templates/files/files_source.html:30 +msgid "Show Source" +msgstr "" + +#: kallithea/templates/files/files_source.html:38 +#, python-format +msgid "Edit on Branch:%s" +msgstr "" + +#: kallithea/templates/files/files_source.html:41 +msgid "Editing binary files not allowed" +msgstr "" + +#: kallithea/templates/files/files_source.html:44 +msgid "Editing files allowed only when on branch head revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:45 +msgid "Deleting files allowed only when on branch head revision" +msgstr "" + +#: kallithea/templates/files/files_source.html:63 +#, python-format +msgid "Binary file (%s)" +msgstr "" + +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "" + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." +msgstr "" + +#: kallithea/templates/files/files_ypjax.html:5 +msgid "annotation" +msgstr "" + +#: kallithea/templates/files/files_ypjax.html:23 +msgid "Go Back" +msgstr "" + +#: kallithea/templates/files/files_ypjax.html:24 +msgid "No files at given path" +msgstr "" + +#: kallithea/templates/followers/followers.html:5 +#, python-format +msgid "%s Followers" +msgstr "" + +#: kallithea/templates/followers/followers.html:9 +#: kallithea/templates/summary/summary.html:142 +#: kallithea/templates/summary/summary.html:143 +msgid "Followers" +msgstr "" + +#: kallithea/templates/followers/followers_data.html:12 +msgid "Started following -" +msgstr "" + +#: kallithea/templates/forks/fork.html:5 +#, python-format +msgid "Fork repository %s" +msgstr "" + +#: kallithea/templates/forks/fork.html:27 +msgid "Fork name" +msgstr "" + +#: kallithea/templates/forks/fork.html:62 +msgid "Default revision for files page, downloads, whoosh, and readme." +msgstr "" + +#: kallithea/templates/forks/fork.html:68 +msgid "Private" +msgstr "" + +#: kallithea/templates/forks/fork.html:77 +msgid "Copy permissions" +msgstr "" + +#: kallithea/templates/forks/fork.html:81 +msgid "Copy permissions from forked repository" +msgstr "" + +#: kallithea/templates/forks/fork.html:87 +msgid "Update after clone" +msgstr "" + +#: kallithea/templates/forks/fork.html:91 +msgid "Checkout source after making a clone" +msgstr "" + +#: kallithea/templates/forks/fork.html:96 +msgid "Fork this Repository" +msgstr "" + +#: kallithea/templates/forks/forks.html:5 +#, python-format +msgid "%s Forks" +msgstr "" + +#: kallithea/templates/forks/forks.html:9 +#: kallithea/templates/summary/summary.html:148 +#: kallithea/templates/summary/summary.html:149 +msgid "Forks" +msgstr "" + +#: kallithea/templates/forks/forks_data.html:17 +msgid "Forked" +msgstr "" + +#: kallithea/templates/forks/forks_data.html:30 +msgid "There are no forks yet" +msgstr "" + +#: kallithea/templates/journal/journal.html:21 +msgid "ATOM journal feed" +msgstr "" + +#: kallithea/templates/journal/journal.html:22 +msgid "RSS journal feed" +msgstr "" + +#: kallithea/templates/journal/journal.html:56 +msgid "My Repositories" +msgstr "" + +#: kallithea/templates/journal/journal_data.html:43 +msgid "No entries yet" +msgstr "" + +#: kallithea/templates/journal/public_journal.html:13 +msgid "ATOM public journal feed" +msgstr "" + +#: kallithea/templates/journal/public_journal.html:14 +msgid "RSS public journal feed" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:4 +#: kallithea/templates/pullrequests/pullrequest.html:8 +msgid "New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:31 +#: kallithea/templates/pullrequests/pullrequest_data.html:15 +#: kallithea/templates/pullrequests/pullrequest_show.html:29 +#: kallithea/templates/pullrequests/pullrequest_show.html:54 +msgid "Title" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:34 +msgid "Summarize the changes - or leave empty" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:43 +#: kallithea/templates/pullrequests/pullrequest_show.html:66 +msgid "Write a short description on this pull request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:49 +msgid "Changeset flow" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:56 +msgid "Origin repository" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest.html:72 +msgid "Destination repository" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:6 +msgid "No entries" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:14 +msgid "Vote" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:18 +msgid "From" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:19 +msgid "To" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:28 +#, python-format +msgid "You voted: %s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:30 +msgid "You didn't vote" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:35 +msgid "(no title)" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:37 +#: kallithea/templates/pullrequests/pullrequest_show.html:31 +#: kallithea/templates/pullrequests/pullrequest_show.html:83 +msgid "Closed" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:67 +msgid "Delete Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:68 +msgid "Confirm to delete this pull request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_data.html:70 +#, python-format +msgid "Confirm again to delete this pull request with %s comments" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:6 +#, python-format +msgid "%s Pull Request %s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:10 +#, python-format +msgid "Pull request %s from %s#%s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:57 +msgid "Summarize the changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:74 +msgid "Reviewer voting result" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:80 +#: kallithea/templates/pullrequests/pullrequest_show.html:81 +msgid "Pull request status calculated from votes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:93 +msgid "Still not reviewed by" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:97 +#, python-format +msgid "%d reviewer" +msgid_plural "%d reviewers" +msgstr[0] "" +msgstr[1] "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:99 +msgid "Pull request was reviewed by all reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:101 +msgid "There are no reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:107 +msgid "Origin" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:113 +msgid "on" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:120 +msgid "Target" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:124 +msgid "This is just a range of changesets and doesn't have a target or a real merge ancestor." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:133 +msgid "Pull changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:173 +msgid "Update" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:191 +msgid "Current revision - no change" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "Pull requests do not change once created. Select a revision and save to replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 +msgid "Pull Request Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:249 +msgid "Remove reviewer" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +msgid "Potential Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 +msgid "Click to add the repository owner as reviewer:" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:295 +msgid "Save Changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 +msgid "Cancel Changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:307 +msgid "Pull Request Content" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:6 +#, python-format +msgid "%s Pull Requests" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:11 +#, python-format +msgid "Pull Requests from '%s'" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:13 +#, python-format +msgid "Pull Requests to '%s'" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:32 +msgid "Open New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:37 +#, python-format +msgid "Show Pull Requests to %s" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:39 +#, python-format +msgid "Show Pull Requests from '%s'" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:49 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:28 +msgid "Hide closed pull requests (only show open pull requests)" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_all.html:51 +#: kallithea/templates/pullrequests/pullrequest_show_my.html:30 +msgid "Show closed pull requests (in addition to open pull requests)" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_my.html:35 +msgid "Pull Requests Created by Me" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show_my.html:38 +msgid "Pull Requests I Participate In" +msgstr "" + +#: kallithea/templates/search/search.html:6 +#, python-format +msgid "%s Search" +msgstr "" + +#: kallithea/templates/search/search.html:8 +#: kallithea/templates/search/search.html:16 +msgid "Search in All Repositories" +msgstr "" + +#: kallithea/templates/search/search.html:50 +msgid "Search term" +msgstr "" + +#: kallithea/templates/search/search.html:62 +msgid "Search in" +msgstr "" + +#: kallithea/templates/search/search.html:65 +msgid "File contents" +msgstr "" + +#: kallithea/templates/search/search.html:66 +msgid "Commit messages" +msgstr "" + +#: kallithea/templates/search/search.html:67 +msgid "File names" +msgstr "" + +#: kallithea/templates/search/search_commit.html:35 +#: kallithea/templates/search/search_content.html:21 +#: kallithea/templates/search/search_path.html:15 +msgid "Permission denied" +msgstr "" + +#: kallithea/templates/summary/statistics.html:4 +#, python-format +msgid "%s Statistics" +msgstr "" + +#: kallithea/templates/summary/statistics.html:16 +#: kallithea/templates/summary/summary.html:39 +#, python-format +msgid "%s ATOM feed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:17 +#: kallithea/templates/summary/summary.html:40 +#, python-format +msgid "%s RSS feed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:36 +#: kallithea/templates/summary/summary.html:100 +#: kallithea/templates/summary/summary.html:116 +msgid "Enable" +msgstr "" + +#: kallithea/templates/summary/statistics.html:39 +msgid "Stats gathered: " +msgstr "" + +#: kallithea/templates/summary/statistics.html:89 +#: kallithea/templates/summary/summary.html:349 +msgid "files" +msgstr "" + +#: kallithea/templates/summary/statistics.html:113 +#: kallithea/templates/summary/summary.html:373 +msgid "Show more" +msgstr "" + +#: kallithea/templates/summary/statistics.html:390 +msgid "commits" +msgstr "" + +#: kallithea/templates/summary/statistics.html:391 +msgid "files added" +msgstr "" + +#: kallithea/templates/summary/statistics.html:392 +msgid "files changed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:393 +msgid "files removed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:395 +msgid "commit" +msgstr "" + +#: kallithea/templates/summary/statistics.html:396 +msgid "file added" +msgstr "" + +#: kallithea/templates/summary/statistics.html:397 +msgid "file changed" +msgstr "" + +#: kallithea/templates/summary/statistics.html:398 +msgid "file removed" +msgstr "" + +#: kallithea/templates/summary/summary.html:4 +#, python-format +msgid "%s Summary" +msgstr "" + +#: kallithea/templates/summary/summary.html:13 +#, python-format +msgid "Repository locked by %s" +msgstr "" + +#: kallithea/templates/summary/summary.html:15 +msgid "Repository unlocked" +msgstr "" + +#: kallithea/templates/summary/summary.html:22 +msgid "Fork of" +msgstr "" + +#: kallithea/templates/summary/summary.html:29 +msgid "Clone from" +msgstr "" + +#: kallithea/templates/summary/summary.html:72 +msgid "Clone URL" +msgstr "" + +#: kallithea/templates/summary/summary.html:78 +msgid "Show by Name" +msgstr "" + +#: kallithea/templates/summary/summary.html:79 +msgid "Show by ID" +msgstr "" + +#: kallithea/templates/summary/summary.html:92 +msgid "Trending files" +msgstr "" + +#: kallithea/templates/summary/summary.html:108 +msgid "Download" +msgstr "" + +#: kallithea/templates/summary/summary.html:112 +msgid "There are no downloads yet" +msgstr "" + +#: kallithea/templates/summary/summary.html:114 +msgid "Downloads are disabled for this repository" +msgstr "" + +#: kallithea/templates/summary/summary.html:120 +msgid "Download as zip" +msgstr "" + +#: kallithea/templates/summary/summary.html:125 +msgid "Check this to download archive with subrepos" +msgstr "" + +#: kallithea/templates/summary/summary.html:125 +msgid "With subrepos" +msgstr "" + +#: kallithea/templates/summary/summary.html:156 +msgid "Repository Size" +msgstr "" + +#: kallithea/templates/summary/summary.html:163 +#: kallithea/templates/summary/summary.html:165 +msgid "Feed" +msgstr "" + +#: kallithea/templates/summary/summary.html:186 +msgid "Latest Changes" +msgstr "" + +#: kallithea/templates/summary/summary.html:188 +msgid "Quick Start" +msgstr "" + +#: kallithea/templates/summary/summary.html:202 +#, python-format +msgid "Readme file from revision %s:%s" +msgstr "" + +#: kallithea/templates/summary/summary.html:293 +#, python-format +msgid "Download %s as %s" +msgstr "" + +#: kallithea/templates/tags/tags.html:5 +#, python-format +msgid "%s Tags" +msgstr "" + +#: kallithea/templates/tags/tags.html:26 +msgid "Compare Tags" +msgstr "" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/fr/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/fr/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/fr/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -7,24 +7,24 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" -"PO-Revision-Date: 2015-09-10 15:13+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" +"PO-Revision-Date: 2016-03-21 13:00+0000\n" "Last-Translator: Étienne Gilli \n" "Language-Team: French " -"\n" +"\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 2.4-dev\n" - -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +"X-Generator: Weblate 2.6-dev\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Il n’y a aucun changement pour le moment" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -36,37 +36,29 @@ msgid "None" msgstr "Aucun" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(fermé)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "Afficher les espaces et tabulations" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "Ignorer les espaces et tabulations" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "Augmenter le contexte du diff à %(num)s lignes" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "Une telle révision n'existe pas pour ce dépôt" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" -"La modification de l'état sur un ensemble de modifications associé à une " -"demande de tirage fermé n'est pas autorisé" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "Sélectionner le changeset" @@ -122,10 +114,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "Cet ensemble de changements était trop important et a été découpé…" @@ -134,117 +126,117 @@ msgid "%s committed on %s" msgstr "%s a commité, le %s" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "Ajouter un nouveau fichier" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "Il n'y a actuellement pas de fichiers. %s" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "%s à %s" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "Ce dépôt a été verrouillé par %s sur %s" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" msgstr "" -"Vous pouvez supprimer uniquement les fichiers avec révision étant dans " -"une branche valide " - -#: kallithea/controllers/files.py:328 +"Vous ne pouvez supprimer que les fichiers dont la révision est une branche " +"valide" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "Le fichier %s a été supprimé via Kallithea" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "Suppression du fichier %s effectuée avec succès" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "Une erreur est survenue durant le commit" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" msgstr "" -"Vous pouvez modifier uniquement les fichiers dont la révision est dans " -"une branche valide " - -#: kallithea/controllers/files.py:391 +"Vous ne pouvez modifier que les fichiers dont la révision est une branche " +"valide" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "%s édité via Kallithea" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Aucun changement" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "Commit réalisé avec succès sur %s" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "%s ajouté par Kallithea" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Aucun contenu" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "Aucun nom de fichier" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" "Le chemin doit être un chemin relatif et ne doit pas contenir .. dans le " "chemin" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "Les téléchargements sont désactivés" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Révision %s inconnue" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Dépôt vide" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "Type d’archive inconnu" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Changesets" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "Branches" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Tags" @@ -258,7 +250,7 @@ msgid "Groups" msgstr "Groupes" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -266,23 +258,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Dépôts" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "Branche" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "Branches fermées" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "Étiquette" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "Signet" @@ -293,103 +289,100 @@ msgstr "Journal public" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "Historique" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "Mauvais captcha" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "Vous vous êtes inscrits avec succès à Kallithea" - -#: kallithea/controllers/login.py:202 -#| msgid "Your password reset link was sent" +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "Vous vous êtes inscrits avec succès à %s" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" -msgstr "" -"Un lien de confirmation de réinitialisation de mot de passe a été envoyé" - -#: kallithea/controllers/login.py:251 -#| msgid "Password reset link" +msgstr "Un lien de confirmation de réinitialisation de mot de passe a été envoyé" + +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" msgstr "Clé de réinitialisation de mot de passe invalide" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "Mot de passe mis à jour avec succès" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (fermé)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "Changements" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "Spécial" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "Branches appairées" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Signets" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "Erreur de création de la demande de pull : %s" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "Aucune description" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "La requête de pull a été ouverte avec succès" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "Reviewer spécifié \"%s\" non valide" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "Une erreur est survenue durant la création de la pull request" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "Changeset manquant depuis la précédente pull request :" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "Nouveau changeset sur %s %s depuis la précédente pull request :" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" "L'ancêtre n'a pas changé - montrer les différences avec la version " "précédente :" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " @@ -398,60 +391,65 @@ "Cette demande de pull est basée sur une autre révision %s et il n'y a pas" " de diff simple." -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "Aucun changement constaté sur %s %s depuis la version précédente." -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "Fermé, remplacé par %s." -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "Mise à jour de la pull request créée" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "Pull request mise à jour" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "La requête de pull a été supprimée avec succès" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "Cette pull request a déjà été fusionnée à %s." -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "Cette pull request a été fermée et ne peut pas être mise à jour." -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "Cette demande de pull peut être mise à jour avec les modifications de %s :" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "Les modifications suivantes sont disponibles sur %s :" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "Pas de changeset trouvé pour ce pull request." -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "Note: La branche %s a une autre tête: %s." -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "Le smises à jour des Git pull requests ne sont pas encore supportées." -#: kallithea/controllers/pullrequests.py:722 +#: kallithea/controllers/pullrequests.py:727 msgid "No permission to change pull request status" msgstr "Permission manquante pour changer le statut du pull request" -#: kallithea/controllers/pullrequests.py:727 +#: kallithea/controllers/pullrequests.py:738 +#, python-format +msgid "Successfully deleted pull request %s" +msgstr "La requête de pull %s a été supprimée avec succès" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "Fermeture." @@ -469,12 +467,12 @@ msgid "An error occurred during search operation." msgstr "Une erreur est survenue pendant la recherche." -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "Aucune donnée actuellement disponible" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "La mise à jour des statistiques est désactivée pour ce dépôt" @@ -497,64 +495,64 @@ msgid "Error occurred during update of defaults" msgstr "Une erreur est survenue durant la mise à jour des réglages par défaut" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 -#: kallithea/controllers/admin/users.py:285 +#: kallithea/controllers/admin/users.py:284 msgid "Forever" msgstr "Pour toujours" +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/users.py:285 +msgid "5 minutes" +msgstr "5 minute" + #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" -msgstr "5 minute" +msgid "1 hour" +msgstr "1 heure" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "1 heure" +msgid "1 day" +msgstr "1 jour" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "1 jour" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "1 mois" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "Toujours" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Une erreur est survenue lors de la création du gist" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "Gist %s supprimé" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "Non modifié" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "Le contenu du gist a été mis à jour avec succès" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "Les données du gist on été mises à jour avec succès" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "Une erreur est survenue durant la mise à jour du gist %s" @@ -571,7 +569,7 @@ msgstr "Votre compte a été mis à jour avec succès" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "Une erreur est survenue durant la mise à jour de l'utilisateur %s" @@ -583,33 +581,33 @@ "l'utilisateur" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "L’e-mail « %s » a été ajouté à l’utilisateur" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "Une erreur est survenue durant l’enregistrement de l’e-mail" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "L’e-mail a été enlevé de l’utilisateur" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "Clé d'API créée avec succès" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "Clé d'API remise à zéro avec succès" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "Clé d'API supprimée avec succès" @@ -659,10 +657,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "Administration" @@ -693,7 +691,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "Activation manuelle du compte externe" @@ -705,7 +703,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "Activation automatique du compte externe" @@ -726,196 +724,196 @@ msgid "Error occurred during update of permissions" msgstr "Une erreur est survenue durant la mise à jour des permissions" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "Une erreur est survenue durant la création du groupe de dépôts %s" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "Groupe de dépôts %s créé" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "Groupe de dépôts %s mis à jour" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "Une erreur est survenue durant la mise à jour du groupe de dépôts %s" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "Ce groupe contient %s dépôts et ne peut être supprimé" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "Ce groupe contient %s sous-groupes et ne peut pas être supprimé" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "Groupe de dépôts %s supprimé" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "Une erreur est survenue durant la suppression du groupe de dépôts %s" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "Impossible de révoquer votre permission d'administrateur" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "Permissions du groupe de dépôts mises à jour" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "Une erreur est survenue durant la révocation de la permission" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "Erreur de création du dépôt %s" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "Dépôt %s créé depuis %s" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "dépôt %s forké en tant que %s" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "Dépôt %s créé" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "Dépôt %s mis à jour avec succès" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "Une erreur est survenue durant la mise à jour du dépôt %s" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "%s forks détachés" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "%s forks supprimés" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "Dépôt %s supprimé" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "Impossible de supprimer le dépôt %s : des forks y sont attachés" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "Erreur pendant la suppression de %s" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "Permissions du dépôt mises à jour" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "Une erreur est survenue durant la création du champ" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "Une erreur est survenue durant la suppression du champ" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "-- Pas un fork --" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "La visibilité du dépôt dans le journal public a été mise à jour" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 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" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "[Aucun dépôt]" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "Le dépôt %s a été marké comme fork de %s" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "Une erreur est survenue durant cette opération" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 msgid "Repository has been locked" msgstr "Ce dépôt a été verrouillé" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 msgid "Repository has been unlocked" msgstr "Ce dépôt a été déverrouillé" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "Une erreur est survenue durant le déverrouillage" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "Invalidation du cache réalisée avec succès" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "Une erreur est survenue durant l’invalidation du cache" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "Les changements distants ont été récupérés" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "Une erreur est survenue durant le pull depuis la source distante" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "Une erreur est survenue durant la suppression des statistiques du dépôt" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "Réglages des gestionnaires de versions mis à jour" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" @@ -923,53 +921,53 @@ "Impossible d'activer la prise en charge de hgsubversion. La bibliothèque " "« hgsubversion » est manquante" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" "Une erreur est survenue durant la mise à jour des réglages de " "l'application" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "Dépôts ré-analysés avec succès. Ajouté : %s. Supprimé : %s." -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "Réglages mis à jour" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "Réglages d’affichage mis à jour" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" "Une erreur est survenue durant la mise à jour des réglages de " "visualisation" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "Veuillez entrer votre adresse e-mail" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "Tâche d'envoi d'e-mail créée" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "Le nouveau hook a été ajouté" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "Hooks mis à jour" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "Une erreur est survenue durant la création du hook" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "La tâche de réindexation Whoosh a été planifiée" @@ -1010,76 +1008,82 @@ msgstr "Permissions du groupe d'utilisateurs mises à jour" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "Permissions mises à jour" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "Une erreur est survenue durant l’enregistrement des permissions" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "Utilisateur %s créé" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "Une erreur est survenue durant la création de l'utilisateur %s" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "L’utilisateur a été mis à jour avec succès" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "Utilisateur supprimé avec succès" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "Une erreur est survenue durant la suppression de l’utilisateur" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "L'utilisateur par défaut ne peut pas être modifié" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "L'adresse IP %s a été ajoutée à la liste blanche" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "Une erreur est survenue durant la sauvegarde d'IP" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "L'adresse IP a été supprimée de la liste blanche" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "IP %s non autorisée" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "Clé d'API invalide" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" +"Une fuite de jeton CSRF a été détectée - tous les jetons de formulaire ont " +"été marqués « expirés »" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "Vous devez être un utilisateur enregistré pour effectuer cette action" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "Vous devez être connecté pour visualiser cette page" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "Dépôt non trouvé sur le système de fichiers" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "Ensemble de changements non trouvé" @@ -1097,125 +1101,125 @@ msgid "No changes detected" msgstr "Aucun changement détecté" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "Branche supprimée : %s" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "Étiquette créée : %s" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "Afficher les changements combinés %s->%s" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "Vue de comparaison" -#: kallithea/lib/helpers.py:696 +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "et" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "%s de plus" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "révisions" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "Nom du fork %s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "Requête de pull %s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "[a supprimé] le dépôt" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "[a créé] le dépôt" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "[a créé] le dépôt en tant que fork" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "[a forké] le dépôt" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "[a mis à jour] le dépôt" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "[téléchargée] archive depuis le dépôt" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "[a supprimé] le dépôt" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "[a créé] l’utilisateur" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "[a mis à jour] l’utilisateur" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "[créé] groupe d'utilisateurs" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "[mis à jour] groupe d'utilisateurs" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "[a commenté] une révision du dépôt" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "[a commenté] la requête de pull pour" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "[a fermé] la requête de pull de" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[a pushé] dans" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "[a commité via Kallithea] dans le dépôt" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "[a pullé depuis un site distant] dans le dépôt" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "[a pullé] depuis" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "[suit maintenant] le dépôt" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "[ne suit plus] le dépôt" @@ -1225,8 +1229,8 @@ msgstr " et %s de plus" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "Aucun fichier" @@ -1250,7 +1254,7 @@ msgid "chmod" msgstr "chmod" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1261,69 +1265,69 @@ "probablement été créé ou renommé manuellement. Veuillez relancer " "l’application pour rescanner les dépôts" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "%d an" msgstr[1] "%d ans" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d mois" msgstr[1] "%d mois" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d jour" msgstr[1] "%d jours" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d heure" msgstr[1] "%d heures" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minute" msgstr[1] "%d minutes" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "%d seconde" msgstr[1] "%d secondes" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "dans %s" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "Il y a %s" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "dans %s et %s" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "Il y a %s et %s" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "à l’instant" @@ -1422,7 +1426,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Administrateur Kallithea" @@ -1533,7 +1537,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "Approuvée" @@ -1548,7 +1552,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "Rejetée" @@ -1575,7 +1579,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "niveau supérieur" @@ -1722,7 +1726,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "Enregistrement désactivé" @@ -1749,14 +1753,14 @@ msgstr "Enregistrement des utilisateurs avec activation de compte automatique" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" "Création de dépôts activée avec l'accès en écriture vers un groupe de " "dépôts" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" "Création de dépôts désactivée avec l'accès en écriture vers un groupe de " @@ -1767,119 +1771,119 @@ msgid "on line %s" msgstr "à la ligne %s" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[Mention]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux dépôts" -#: kallithea/model/db.py:1668 +#: kallithea/model/db.py:1672 msgid "Default user has read access to new repositories" msgstr "L'utilisateur par défaut a un accès en lecture aux nouveaux dépôts" -#: kallithea/model/db.py:1669 +#: kallithea/model/db.py:1673 msgid "Default user has write access to new repositories" msgstr "L'utilisateur par défaut a un accès en écriture aux nouveaux dépôts" -#: kallithea/model/db.py:1670 +#: kallithea/model/db.py:1674 msgid "Default user has admin access to new repositories" msgstr "L'utilisateur par défaut a un accès administrateur aux nouveaux dépôts" -#: kallithea/model/db.py:1672 +#: kallithea/model/db.py:1676 msgid "Default user has no access to new repository groups" msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux groupes de dépôts" -#: kallithea/model/db.py:1673 +#: kallithea/model/db.py:1677 msgid "Default user has read access to new repository groups" msgstr "" -"L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes de " -"dépôts" - -#: kallithea/model/db.py:1674 +"L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes de" +" dépôts" + +#: kallithea/model/db.py:1678 msgid "Default user has write access to new repository groups" msgstr "" -"L'utilisateur par défaut a accès en écriture aux nouveaux groupes de dépôts" - -#: kallithea/model/db.py:1675 +"L'utilisateur par défaut a accès en écriture aux nouveaux groupes de " +"dépôts" + +#: kallithea/model/db.py:1679 msgid "Default user has admin access to new repository groups" msgstr "" "L'utilisateur par défaut a accès administrateur aux nouveaux groupes de " "dépôts" -#: kallithea/model/db.py:1677 +#: kallithea/model/db.py:1681 msgid "Default user has no access to new user groups" -msgstr "" -"L'utilisateur par défaut n'a pas accès aux nouveaux groupes d'utilisateurs" - -#: kallithea/model/db.py:1678 +msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux groupes d'utilisateurs" + +#: kallithea/model/db.py:1682 msgid "Default user has read access to new user groups" msgstr "" "L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes " "d'utilisateurs" -#: kallithea/model/db.py:1679 +#: kallithea/model/db.py:1683 msgid "Default user has write access to new user groups" msgstr "" "L'utilisateur par défaut a accès en écriture aux nouveaux groupes " "d'utilisateurs" -#: kallithea/model/db.py:1680 +#: kallithea/model/db.py:1684 msgid "Default user has admin access to new user groups" msgstr "" "L'utilisateur par défaut a un accès administrateur aux nouveaux groupes " "d'utilisateurs" -#: kallithea/model/db.py:1682 +#: kallithea/model/db.py:1686 msgid "Only admins can create repository groups" msgstr "Seul un administrateur peut créer un groupe de dépôts" -#: kallithea/model/db.py:1683 +#: kallithea/model/db.py:1687 msgid "Non-admins can create repository groups" -msgstr "" -"Les utilisateurs non-administrateurs peuvent créer des groupes de dépôts" - -#: kallithea/model/db.py:1685 +msgstr "Les utilisateurs non-administrateurs peuvent créer des groupes de dépôts" + +#: kallithea/model/db.py:1689 msgid "Only admins can create user groups" msgstr "Seul un administrateur peut créer des groupes d'utilisateurs" -#: kallithea/model/db.py:1686 +#: kallithea/model/db.py:1690 msgid "Non-admins can create user groups" msgstr "" -"Les utilisateurs non-administrateurs peuvent créer des groupes d'utilisateurs" - -#: kallithea/model/db.py:1688 +"Les utilisateurs non-administrateurs peuvent créer des groupes " +"d'utilisateurs" + +#: kallithea/model/db.py:1692 msgid "Only admins can create top level repositories" msgstr "Seul un administrateur peut créer des dépôts de niveau supérieur" -#: kallithea/model/db.py:1689 +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" "Les utilisateurs non-administrateurs peuvent créer des dépôts de niveau " "supérieur" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 msgid "Only admins can fork repositories" msgstr "Seul un administrateur peut faire un fork de dépôt" -#: kallithea/model/db.py:1695 -msgid "Non-admins can can fork repositories" +#: kallithea/model/db.py:1699 +msgid "Non-admins can fork repositories" msgstr "Les utilisateurs non-administrateurs peuvent faire un fork de dépôt" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 msgid "User registration with manual account activation" msgstr "Enregistrement des utilisateurs avec activation de compte manuelle" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "Enregistrement des utilisateurs avec activation de compte automatique" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 msgid "Not reviewed" msgstr "Pas encore relue" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 msgid "Under review" msgstr "En cours de relecture" @@ -1901,7 +1905,7 @@ msgid "Enter %(min)i characters or more" msgstr "Entrez au moins %(min)i caractères" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "Le nom ne doit pas contenir seulement des chiffres" @@ -1977,7 +1981,6 @@ #: kallithea/model/notification.py:307 #, python-format -#| msgid "%(user)s wants you to review pull request %(pr_nice_id)s:" msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "" "[Ajouté] Demande de pull %(pr_nice_id)s à partir de %(ref)s pour " @@ -1985,7 +1988,6 @@ #: kallithea/model/notification.py:308 #, python-format -#| msgid "[commented] on pull request for" msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "" "[Commentaire] Demande de pull %(pr_nice_id)s à partir de %(ref)s pour " @@ -2002,7 +2004,7 @@ "%(user)s veut que vous regardiez la demande de pull %(pr_nice_id)s : " "%(pr_title)s" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "Dernier sommet" @@ -2013,8 +2015,8 @@ #: kallithea/model/user.py:256 msgid "You can't remove this user since it is crucial for the entire application" msgstr "" -"Vous ne pouvez pas supprimer cet utilisateur ; il est nécessaire pour le bon " -"fonctionnement de l’application" +"Vous ne pouvez pas supprimer cet utilisateur ; il est nécessaire pour le " +"bon fonctionnement de l’application" #: kallithea/model/user.py:261 #, python-format @@ -2040,20 +2042,19 @@ "User \"%s\" still owns %s user groups and cannot be removed. Switch " "owners or remove those user groups: %s" msgstr "" -"L’utilisateur « %s » possède %s groupes d'utilisateurs et ne peut pas être " -"supprimé. Changez les propriétaires de ces groupes d'utilisateurs ou " -"supprimez-les : %s" - -#: kallithea/model/user.py:360 +"L’utilisateur « %s » possède %s groupes d'utilisateurs et ne peut pas " +"être supprimé. Changez les propriétaires de ces groupes d'utilisateurs ou" +" supprimez-les : %s" + +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "Lien de remise à zéro du mot de passe" -#: kallithea/model/user.py:408 -#| msgid "Password reset link" +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "Notification de réinitialisation du mot de passe" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -2066,44 +2067,44 @@ msgid "Value cannot be an empty list" msgstr "Cette valeur ne peut être une liste vide" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "Le nom d’utilisateur « %(username)s » existe déjà" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "Le nom d’utilisateur « %(username)s » n’est pas valide" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -"Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, " -"des underscores (_), points, traits d'union et doit commencer avec un " +"Le nom d'utilisateur ne peut contenir que des caractères alphanumériques," +" des underscores (_), points, traits d'union et doit commencer avec un " "caractère alphanumérique ou un underscore" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "L'entrée n'est pas valide" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "Le nom d’utilisateur « %(username)s » n’est pas valide" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "Nom de groupe d'utilisateurs invalide" -#: kallithea/model/validators.py:153 +#: kallithea/model/validators.py:155 #, python-format msgid "User group \"%(usergroup)s\" already exists" msgstr "Le groupe d'utilisateurs « %(usergroup)s » existe déjà" -#: kallithea/model/validators.py:155 +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" @@ -2112,106 +2113,106 @@ "alphanumériques, des tirets, des points, des traits d'union et doit " "commencer avec un caractère alphanumérique" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "Impossible d’assigner ce groupe en tant que parent" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "Le groupe « %(group_name)s » existe déjà" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "Un dépôt portant le nom « %(group_name)s » existe déjà" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "Caractères incorrects (non-ASCII) dans le mot de passe" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "Ancien mot de passe invalide" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "Les mots de passe ne correspondent pas" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "Nom d'utilisateur ou mot de passe invalide" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "Jeton d’authentification incorrect" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "Le nom de dépôt « %(repo)s » n’est pas autorisé" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "Un dépôt portant le nom « %(repo)s » existe déjà" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "Le dépôt « %(repo)s » existe déjà dans le groupe « %(group)s »" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "Un groupe de dépôts avec le nom « %(repo)s » existe déjà" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 msgid "Invalid repository URL" msgstr "URL de dépôt invalide" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -"URL de dépôt invalide. Ce doit être une URL valide de type http, https, ssh, " -"svn+http ou svn+https" - -#: kallithea/model/validators.py:489 +"URL de dépôt invalide. Ce doit être une URL valide de type http, https, " +"ssh, svn+http ou svn+https" + +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "Le fork doit être du même type que le parent" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "Vous n’avez pas la permission de créer un dépôt dans ce" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "pas de permission de créer un dépôt dans la racine" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "Vous n'avez pas les permissions pour créer un groupe dans cet endroit" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "Ce nom d'utilisateur ou nom de groupe d'utilisateurs n'est pas valide" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "Ceci n’est pas un chemin valide" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" msgstr "Cette adresse e-mail est déjà enregistrée" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "L’adresse e-mail « %(email)s » n’existe pas" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" @@ -2219,26 +2220,26 @@ "L’attribut Login du CN doit être spécifié. Cet attribut correspond au nom" " d’utilisateur" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "Veuillez entrer une adresse IPv4 ou IPv6 valide" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "La taille du réseau (bits) doit être entre 0 et 32 (et non %(bits)r)" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" "Le nom de la clé ne peut consister que de letters, de traits d'union, de " "tirets ou de nombres" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "Le nom du fichier ne peut être à l'intérieur d'un répertoire" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2367,7 +2368,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "Propriétaire" @@ -2415,7 +2416,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2425,7 +2426,7 @@ msgstr "Chargement…" #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "Connexion" @@ -2440,7 +2441,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "Nom d’utilisateur" @@ -2448,7 +2449,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "Mot de passe" @@ -2460,7 +2461,7 @@ msgid "Forgot your password ?" msgstr "Mot de passe oublié ?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "Vous n’avez pas de compte ?" @@ -2498,39 +2499,37 @@ msgstr "Envoyer l'E-mail de réinitialisation du mot de passe" #: kallithea/templates/password_reset.html:47 -#| msgid "" " msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." msgstr "" -"Un lien de réinitialisation du mot de passe sera envoyé à l'adresse e-mail " -"indiquée si elle est enregistrée dans le système." +"Un lien de réinitialisation du mot de passe sera envoyé à l'adresse " +"e-mail indiquée si elle est enregistrée dans le système." #: kallithea/templates/password_reset_confirmation.html:19 #, python-format msgid "You are about to set a new password for the email address %s." msgstr "" -"Vous êtes sur le point de changer le mot de passe pour l'adresse e-mail %s." +"Vous êtes sur le point de changer le mot de passe pour l'adresse e-mail " +"%s." #: kallithea/templates/password_reset_confirmation.html:20 msgid "" "Note that you must use the same browser session for this as the one used " "to request the password reset." msgstr "" -"Vous devez utiliser la même session de navigateur pour cette opération que " -"celle utilisée pour la demande de réinitialisation de mot de passe." +"Vous devez utiliser la même session de navigateur pour cette opération " +"que celle utilisée pour la demande de réinitialisation de mot de passe." #: kallithea/templates/password_reset_confirmation.html:30 msgid "Code you received in the email" msgstr "Le code que vous avez reçu dans l'e-mail" #: kallithea/templates/password_reset_confirmation.html:39 -#| msgid "New password" msgid "New Password" msgstr "Nouveau mot de passe" #: kallithea/templates/password_reset_confirmation.html:48 -#| msgid "Confirm new password" msgid "Confirm New Password" msgstr "Confirmer le nouveau mot de passe" @@ -2579,8 +2578,8 @@ #: kallithea/templates/register.html:92 msgid "Registered accounts are ready to use and need no further action." msgstr "" -"Les comptes enregistrés sont prêts à être utilisés, et ne nécessitent aucune " -"autre action." +"Les comptes enregistrés sont prêts à être utilisés, et ne nécessitent " +"aucune autre action." #: kallithea/templates/register.html:94 msgid "Please wait for an administrator to activate your account." @@ -2591,10 +2590,6 @@ msgid "There are no branches yet" msgstr "Aucune branche n’a été créée pour le moment" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "Branches fermées" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2686,8 +2681,9 @@ "Comma-separated list of plugins; Kallithea will try user authentication " "in plugin order" msgstr "" -"Une liste séparée avec des virgules des greffons. L'ordre des greffons est " -"aussi celui dans lequel Kallithea va essayer d'authentifier un utilisateur" +"Une liste séparée avec des virgules des greffons. L'ordre des greffons " +"est aussi celui dans lequel Kallithea va essayer d'authentifier un " +"utilisateur" #: kallithea/templates/admin/auth/auth_settings.html:34 msgid "Available built-in plugins" @@ -2827,12 +2823,12 @@ msgid "Never" msgstr "Jamais" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "Mettre à jour le gist" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "Annuler" @@ -2855,7 +2851,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "Créer un nouveau gist" @@ -2943,7 +2939,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2963,8 +2960,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2991,13 +2986,12 @@ msgstr "créé" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "Montrer en brut" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "Mon compte" @@ -3120,8 +3114,7 @@ #: kallithea/templates/admin/my_account/my_account_password.html:45 #, python-format msgid "This account is managed with %s and the password cannot be changed here" -msgstr "" -"Ce compte est géré avec %s et le mot de passe ne peut pas être changé ici" +msgstr "Ce compte est géré avec %s et le mot de passe ne peut pas être changé ici" #: kallithea/templates/admin/my_account/my_account_profile.html:11 msgid "Change your avatar at" @@ -3179,7 +3172,7 @@ msgstr "Commentaires" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "Demandes de pull" @@ -3197,7 +3190,7 @@ msgstr "Montrer Notification" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "Notifications" @@ -3236,9 +3229,9 @@ "permission, note that all custom default permission on repositories will " "be lost" msgstr "" -"Toutes les permissions par défaut de chaque dépôt vont être réinitialisées " -"aux valeurs choisies. Notez que toutes les permissions par défaut " -"personnalisées sur les dépôts seront perdues" +"Toutes les permissions par défaut de chaque dépôt vont être " +"réinitialisées aux valeurs choisies. Notez que toutes les permissions par" +" défaut personnalisées sur les dépôts seront perdues" #: kallithea/templates/admin/permissions/permissions_globals.html:26 msgid "Apply to all existing repositories" @@ -3263,8 +3256,8 @@ "will be lost" msgstr "" "Toutes les permissions par défaut de chaque groupe de dépôts vont être " -"réinitialisées aux valeurs choisies. Notez que toutes les permissions par " -"défaut personnalisées sur les groupes de dépôts seront perdues" +"réinitialisées aux valeurs choisies. Notez que toutes les permissions par" +" défaut personnalisées sur les groupes de dépôts seront perdues" #: kallithea/templates/admin/permissions/permissions_globals.html:40 msgid "Apply to all existing repository groups" @@ -3273,7 +3266,8 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:41 msgid "Permissions for the Default user on new repository groups." msgstr "" -"Permissions pour l'utilisateur par défaut sur les nouveaux groupes de dépôts." +"Permissions pour l'utilisateur par défaut sur les nouveaux groupes de " +"dépôts." #: kallithea/templates/admin/permissions/permissions_globals.html:46 #: kallithea/templates/data_table/_dt_elements.html:209 @@ -3286,9 +3280,10 @@ "permission, note that all custom default permission on user groups will " "be lost" msgstr "" -"Toutes les permissions par défaut de chaque groupe d'utilisateurs vont être " -"réinitialisées aux valeurs choisies. Notez que toutes les permissions par " -"défaut personnalisées sur les groupes d'utilisateurs seront perdues" +"Toutes les permissions par défaut de chaque groupe d'utilisateurs vont " +"être réinitialisées aux valeurs choisies. Notez que toutes les " +"permissions par défaut personnalisées sur les groupes d'utilisateurs " +"seront perdues" #: kallithea/templates/admin/permissions/permissions_globals.html:54 msgid "Apply to all existing user groups" @@ -3307,17 +3302,17 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:64 msgid "Enable this to allow non-admins to create repositories at the top level." msgstr "" -"Activer pour autoriser les non-administrateurs à créer des dépôts au niveau " -"supérieur." +"Activer pour autoriser les non-administrateurs à créer des dépôts au " +"niveau supérieur." #: kallithea/templates/admin/permissions/permissions_globals.html:65 msgid "" "Note: This will also give all users API access to create repositories " "everywhere. That might change in future versions." msgstr "" -"Note : Cela autorisera également tous les utilisateurs à utiliser l'API pour " -"créer des dépôts partout. Ce comportement peut changer dans des versions " -"futures." +"Note : Cela autorisera également tous les utilisateurs à utiliser l'API " +"pour créer des dépôts partout. Ce comportement peut changer dans des " +"versions futures." #: kallithea/templates/admin/permissions/permissions_globals.html:70 msgid "Repository creation with group write access" @@ -3349,8 +3344,7 @@ #: kallithea/templates/admin/permissions/permissions_globals.html:92 msgid "Enable this to allow non-admins to fork repositories." -msgstr "" -"Activer pour autoriser les non-administrateurs à faire des fork de dépôt." +msgstr "Activer pour autoriser les non-administrateurs à faire des fork de dépôt." #: kallithea/templates/admin/permissions/permissions_globals.html:97 msgid "Registration" @@ -3420,7 +3414,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3567,8 +3561,8 @@ "Optional: URL of a remote repository. If set, the repository will be " "created as a clone from this URL." msgstr "" -"Optionnel : URL d'un dépôt distant. Si renseigné, le dépôt sera créé comme " -"un clone à partir de cette URL." +"Optionnel : URL d'un dépôt distant. Si renseigné, le dépôt sera créé " +"comme un clone à partir de cette URL." #: kallithea/templates/admin/repos/repo_add_base.html:32 #: kallithea/templates/admin/repos/repo_edit_settings.html:69 @@ -3626,8 +3620,8 @@ "We're sorry but error occurred during this operation. Please check your " "Kallithea server logs, or contact administrator." msgstr "" -"Désolé, une erreur est survenue pendant l'opération. Vérifiez les journaux " -"du serveur Kallithea, ou contactez votre administrateur." +"Désolé, une erreur est survenue pendant l'opération. Vérifiez les " +"journaux du serveur Kallithea, ou contactez votre administrateur." #: kallithea/templates/admin/repos/repo_edit.html:8 #, python-format @@ -3684,8 +3678,8 @@ "All actions done in this repository will be visible to everyone in the " "public journal." msgstr "" -"Les actions réalisées sur ce dépôt seront visibles à tous depuis le journal " -"public." +"Les actions réalisées sur ce dépôt seront visibles à tous depuis le " +"journal public." #: kallithea/templates/admin/repos/repo_edit_advanced.html:46 msgid "Change Locking" @@ -3699,6 +3693,11 @@ msgid "Unlock Repository" msgstr "Déverrouiller le dépôt" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "Verrouillé par %s sur %s" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "Veuillez confirmer le verrouillage de ce dépôt." @@ -3718,10 +3717,10 @@ "pulling locks the repository; only the user who pulled and locked it can " "unlock it by doing a push." msgstr "" -"Forcer le verrouillage du dépôt. Ne fonctionne que lorsque l'accès anonyme " -"est désactivé. Déclencher un pull verrouille le dépôt. L'utilisateur qui " -"fait le pull verrouille le dépôt ; seul l'utilisateur qui a fait le pull et " -"a verrouillé peut déverrouiller en faisant un push." +"Forcer le verrouillage du dépôt. Ne fonctionne que lorsque l'accès " +"anonyme est désactivé. Déclencher un pull verrouille le dépôt. " +"L'utilisateur qui fait le pull verrouille le dépôt ; seul l'utilisateur " +"qui a fait le pull et a verrouillé peut déverrouiller en faisant un push." #: kallithea/templates/admin/repos/repo_edit_advanced.html:79 #: kallithea/templates/data_table/_dt_elements.html:130 @@ -3754,25 +3753,21 @@ "administrator expires it. The administrator can both permanently delete " "it or restore it." msgstr "" -"Le dépôt supprimé sera mis de côté et caché jusqu'à ce que l'administrateur " -"le fasse expirer. L'administrateur peut soit le supprimer définitivement, " -"soit le restaurer." +"Le dépôt supprimé sera mis de côté et caché jusqu'à ce que " +"l'administrateur le fasse expirer. L'administrateur peut soit le " +"supprimer définitivement, soit le restaurer." #: kallithea/templates/admin/repos/repo_edit_caches.html:4 msgid "Invalidate Repository Cache" msgstr "Invalider le cache du dépôt" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "Voulez-vous vraiment invalider le cache du dépôt ?" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " "repository will be cached again." msgstr "" -"Invalider manuellement le cache de ce dépôt. Au prochain accès sur ce dépôt, " -"il sera à nouveau mis en cache." +"Invalider manuellement le cache de ce dépôt. Au prochain accès sur ce " +"dépôt, il sera à nouveau mis en cache." #: kallithea/templates/admin/repos/repo_edit_caches.html:12 msgid "List of Cached Values" @@ -3844,8 +3839,7 @@ #: kallithea/templates/admin/repos/repo_edit_remote.html:11 msgid "Confirm to pull changes from remote repository." -msgstr "" -"Voulez-vous vraiment récupérer les changements depuis le dépôt distant ?" +msgstr "Voulez-vous vraiment récupérer les changements depuis le dépôt distant ?" #: kallithea/templates/admin/repos/repo_edit_remote.html:17 msgid "This repository does not have a remote repository URL." @@ -3874,11 +3868,11 @@ msgstr "" "Si ce dépôt est renommé ou déplacé dans un autre groupe, l'URL du dépôt " "change.\n" -" L'utilisation de l'URL permanente ci-dessus " -"garantit que ce dépôt sera toujours accessible via cette URL.\n" +" L'utilisation de l'URL permanente ci-" +"dessus garantit que ce dépôt sera toujours accessible via cette URL.\n" " Cela peut être utile pour les systèmes " -"d'intégration continue, ou dans tous les cas où vous devez saisir l'URL « en " -"dur » dans un service tiers." +"d'intégration continue, ou dans tous les cas où vous devez saisir l'URL " +"« en dur » dans un service tiers." #: kallithea/templates/admin/repos/repo_edit_settings.html:21 msgid "Remote repository" @@ -4026,9 +4020,9 @@ "Hooks can be used to trigger actions on certain events such as push / " "pull. They can trigger Python functions or external applications." msgstr "" -"Les hooks peuvent être utilisés pour déclencher des actions lors de certains " -"évènements comme le push et le pull. Ils peuvent déclencher des fonctions " -"Python ou des applications externes." +"Les hooks peuvent être utilisés pour déclencher des actions lors de " +"certains évènements comme le push et le pull. Ils peuvent déclencher des " +"fonctions Python ou des applications externes." #: kallithea/templates/admin/settings/settings_hooks.html:19 msgid "Custom Hooks" @@ -4051,9 +4045,9 @@ "Check this option to remove all comments, pull requests and other records" " related to repositories that no longer exist in the filesystem." msgstr "" -"Cocher cette option pour supprimer tous les commentaires, les requêtes de " -"pull et d'autres informations liées aux dépôts qui n'existent plus sur le " -"système de fichiers." +"Cocher cette option pour supprimer tous les commentaires, les requêtes de" +" pull et d'autres informations liées aux dépôts qui n'existent plus sur " +"le système de fichiers." #: kallithea/templates/admin/settings/settings_mapping.html:17 msgid "Invalidate cache for all repositories" @@ -4061,8 +4055,7 @@ #: kallithea/templates/admin/settings/settings_mapping.html:19 msgid "Check this to reload data and clear cache keys for all repositories." -msgstr "" -"Cocher pour recharger les données et vider le cache pour tous les dépôts." +msgstr "Cocher pour recharger les données et vider le cache pour tous les dépôts." #: kallithea/templates/admin/settings/settings_mapping.html:23 msgid "Install Git hooks" @@ -4073,8 +4066,8 @@ "Verify if Kallithea's Git hooks are installed for each repository. " "Current hooks will be updated to the latest version." msgstr "" -"Vérifier si les hooks Git de Kallithea sont installés pour chaque dépôt. Les " -"hooks actuels seront mis à jour vers la dernière version." +"Vérifier si les hooks Git de Kallithea sont installés pour chaque dépôt. " +"Les hooks actuels seront mis à jour vers la dernière version." #: kallithea/templates/admin/settings/settings_mapping.html:28 msgid "Overwrite existing Git hooks" @@ -4086,9 +4079,9 @@ "not seem to come from Kallithea. WARNING: This operation will destroy any" " custom git hooks you may have deployed by hand!" msgstr "" -"Lors de l'installation des hooks Git, écraser tous les hooks existants, même " -"s'ils ne semblent pas provenir de Kallithea. ATTENTION : cette opération " -"détruira tous les hooks Git que vous avez déployés à la main !" +"Lors de l'installation des hooks Git, écraser tous les hooks existants, " +"même s'ils ne semblent pas provenir de Kallithea. ATTENTION : cette " +"opération détruira tous les hooks Git que vous avez déployés à la main !" #: kallithea/templates/admin/settings/settings_mapping.html:35 msgid "Rescan Repositories" @@ -4107,8 +4100,8 @@ "This option completely reindexeses all of the repositories for proper " "fulltext search capabilities." msgstr "" -"Cette option ré-indexe complètement tous les dépôts pour pouvoir faire des " -"recherches dans le texte complet." +"Cette option ré-indexe complètement tous les dépôts pour pouvoir faire " +"des recherches dans le texte complet." #: kallithea/templates/admin/settings/settings_search.html:21 msgid "Reindex" @@ -4171,9 +4164,9 @@ "Activate to require SSL both pushing and pulling. If SSL certificate is " "missing, it will return an HTTP Error 406: Not Acceptable." msgstr "" -"Activez pour faire en sorte que Kallithea force l'utilisation de SSL pour " -"pousser ou tirer. Si le certificat SSL est manquant, une erreur « HTTP 406: " -"Not Acceptable » sera renvoyée." +"Activez pour faire en sorte que Kallithea force l'utilisation de SSL pour" +" pousser ou tirer. Si le certificat SSL est manquant, une erreur « HTTP " +"406: Not Acceptable » sera renvoyée." #: kallithea/templates/admin/settings/settings_vcs.html:24 msgid "Show repository size after push" @@ -4208,8 +4201,8 @@ "Requires hgsubversion library to be installed. Enables cloning of remote " "Subversion repositories while converting them to Mercurial." msgstr "" -"La bibliothèque hgsubversion doit être installée. Elle permet de cloner des " -"dépôts SVN distants et de les migrer vers Mercurial." +"La bibliothèque hgsubversion doit être installée. Elle permet de cloner " +"des dépôts SVN distants et de les migrer vers Mercurial." #: kallithea/templates/admin/settings/settings_vcs.html:64 msgid "Location of repositories" @@ -4229,7 +4222,8 @@ "value, a restart and rescan of the repository folder are both required." msgstr "" "Emplacement où les dépôts sont stockés sur le système de fichiers. La " -"modification de cette valeur nécessite un re-démarrage et un nouveau scan." +"modification de cette valeur nécessite un re-démarrage et un nouveau " +"scan." #: kallithea/templates/admin/settings/settings_visual.html:8 msgid "General" @@ -4242,7 +4236,8 @@ #: kallithea/templates/admin/settings/settings_visual.html:15 msgid "Allows storing additional customized fields per repository." msgstr "" -"Permet d'enregistrer des champs personnalisés additionnels pour chaque dépôt." +"Permet d'enregistrer des champs personnalisés additionnels pour chaque " +"dépôt." #: kallithea/templates/admin/settings/settings_visual.html:18 msgid "Show Kallithea version" @@ -4250,8 +4245,7 @@ #: kallithea/templates/admin/settings/settings_visual.html:20 msgid "Shows or hides a version number of Kallithea displayed in the footer." -msgstr "" -"Afficher ou cacher le numéro de version de Kallithea dans le pied de page." +msgstr "Afficher ou cacher le numéro de version de Kallithea dans le pied de page." #: kallithea/templates/admin/settings/settings_visual.html:24 msgid "Use Gravatars in Kallithea" @@ -4276,15 +4270,16 @@ "L'URL de Gravatar vous permet d'utiliser un autre serveur d'avatars.\n" " Les variables " "suivantes dans l'URL seront remplacées comme suit :\n" -" {scheme} 'http' " -"ou 'https' envoyé à partir du serveur Kallithea en cours d'utilisation,\n" -" {email} adresse " -"e-mail de l'utilisateur,\n" +" {scheme} " +"'http' ou 'https' envoyé à partir du serveur Kallithea en cours " +"d'utilisation,\n" +" {email} " +"adresse e-mail de l'utilisateur,\n" " {md5email} " "empreinte md5 (hash) de l'adresse e-mail de l'utilisateur (comme sur " "gravatar.com),\n" -" {size} taille " -"de l'image demandée au serveur,\n" +" {size} " +"taille de l'image demandée au serveur,\n" " {netloc} " "emplacement réseau/hôte du serveur Kallithea en cours d'utilisation." @@ -4309,16 +4304,17 @@ "'{scheme}://{user}@{netloc}/{repo}'.\n" " Les variables " "suivantes sont disponibles :\n" -" {scheme} 'http' " -"ou 'https' envoyé à partir du serveur Kallithea en cours d'utilisation,\n" -" {user} nom de " -"l'utilisateur courant,\n" +" {scheme} " +"'http' ou 'https' envoyé à partir du serveur Kallithea en cours " +"d'utilisation,\n" +" {user} nom de" +" l'utilisateur courant,\n" " {netloc} " "emplacement réseau/hôte du serveur Kallithea en cours d'utilisation,\n" " {repo} nom " "complet du dépôt,\n" -" {repoid} ID du " -"dépôt, peut être utilisé pour cloner par ID." +" {repoid} ID du" +" dépôt, peut être utilisé pour cloner par ID." #: kallithea/templates/admin/settings/settings_visual.html:55 msgid "Dashboard items" @@ -4329,8 +4325,8 @@ "Number of items displayed in the main page dashboard before pagination is" " shown." msgstr "" -"Nombre d'éléments affichés dans la page principale du tableau de bord avant " -"d'afficher la pagination." +"Nombre d'éléments affichés dans la page principale du tableau de bord " +"avant d'afficher la pagination." #: kallithea/templates/admin/settings/settings_visual.html:65 msgid "Admin pages items" @@ -4341,8 +4337,8 @@ "Number of items displayed in the admin pages grids before pagination is " "shown." msgstr "" -"Nombre d'éléments affichés dans les grilles des pages admin avant d'afficher " -"la pagination." +"Nombre d'éléments affichés dans les grilles des pages admin avant " +"d'afficher la pagination." #: kallithea/templates/admin/settings/settings_visual.html:75 msgid "Icons" @@ -4513,7 +4509,8 @@ #: kallithea/templates/admin/users/user_edit_profile.html:12 msgid "Missing email, please update this user email address." msgstr "" -"E-mail manquant, veuillez mettre à jour l'adresse e-mail de cet utilisateur." +"E-mail manquant, veuillez mettre à jour l'adresse e-mail de cet " +"utilisateur." #: kallithea/templates/admin/users/user_edit_profile.html:51 msgid "Name in Source of Record" @@ -4574,21 +4571,17 @@ msgid "Files" msgstr "Fichiers" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "Basculer vers" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "Options" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "Comparer le fork" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4598,111 +4591,116 @@ msgid "Compare" msgstr "Comparer" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "Rechercher" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "Déverrouiller" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "Verrouiller" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "Suivre" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "Arrêter de suivre" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "Arrêter de suivre" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "Fork" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "Créer une requête de pull" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "Afficher les requêtes de pull pour %s" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "Basculer vers" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "Aucune correspondance trouvée" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "Afficher l'activité récente" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "Journal public" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "Afficher les gists publics" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "Gists" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "Tous les Gists publics" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "Mes Gists publics" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "Mes Gist privés" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "Recherche dans les dépôts" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "Mes requêtes de pull" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "Non connecté" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "Connexion à votre compte" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "Mot de passe oublié ?" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "Se déconnecter" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "Aucune correspondance trouvée" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "Raccourcis clavier" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "Raccourcis globaux" @@ -4716,8 +4714,8 @@ "Select to inherit global settings, IP whitelist and permissions from the " "%s." msgstr "" -"Sélectionner pour hériter des réglages généraux, de la liste blanche d'IP et " -"des permissions depuis les %s." +"Sélectionner pour hériter des réglages généraux, de la liste blanche d'IP" +" et des permissions depuis les %s." #: kallithea/templates/base/default_perms_box.html:28 msgid "Create repositories" @@ -4726,7 +4724,8 @@ #: kallithea/templates/base/default_perms_box.html:33 msgid "Select this option to allow repository creation for this user" msgstr "" -"Sélectionner cette option pour autoriser cet utilisateur à créer des dépôts" +"Sélectionner cette option pour autoriser cet utilisateur à créer des " +"dépôts" #: kallithea/templates/base/default_perms_box.html:40 msgid "Create user groups" @@ -4735,8 +4734,8 @@ #: kallithea/templates/base/default_perms_box.html:45 msgid "Select this option to allow user group creation for this user" msgstr "" -"Sélectionner cette option pour autoriser cet utilisateur à créer des groupes " -"d'utilisateurs" +"Sélectionner cette option pour autoriser cet utilisateur à créer des " +"groupes d'utilisateurs" #: kallithea/templates/base/default_perms_box.html:52 msgid "Fork repositories" @@ -4745,7 +4744,8 @@ #: kallithea/templates/base/default_perms_box.html:57 msgid "Select this option to allow repository forking for this user" msgstr "" -"Sélectionner cette option pour autoriser cet utilisateur à forker des dépôts" +"Sélectionner cette option pour autoriser cet utilisateur à forker des " +"dépôts" #: kallithea/templates/base/perms_summary.html:13 #: kallithea/templates/changelog/changelog.html:42 @@ -4808,7 +4808,6 @@ msgstr "Aucun fichier correspondant" #: kallithea/templates/base/root.html:31 -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "Ouvrir une nouvelle requête de pull à partir de {0}" @@ -4817,7 +4816,6 @@ msgstr "Ouvrir une nouvelle requête de pull pour {0} → {1}" #: kallithea/templates/base/root.html:33 -#| msgid "Show Selected Changeset __S" msgid "Show Selected Changesets {0} → {1}" msgstr "Afficher les changesets sélectionnés {0} → {1}" @@ -4827,6 +4825,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "Replier le Diff" @@ -4934,54 +4933,58 @@ #: kallithea/templates/changelog/changelog.html:92 #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format +#| msgid "" "Changeset status: %s\n" "Click to open associated pull request %s" msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" -"Statut du changeset : %s\n" +"Statut du changeset : %s par %s\n" "Cliquer pour ouvrir la requête de pull %s associée" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "Statut de changeset : %s" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, python-format +#| msgid "Changeset status: %s" +msgid "Changeset status: %s by %s" +msgstr "Statut de changeset : %s par %s" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "Développer le message de commit" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "Le changeset a des commentaires" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "Marque-page %s" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "Tag %s" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "Branche %s" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "Il n’y a aucun changement pour le moment" @@ -4997,7 +5000,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "Ajouté" @@ -5027,21 +5030,21 @@ msgid "Refs" msgstr "Refs" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "Ajouter ou téléverser des fichiers directement via Kallithea" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "Ajouter un nouveau fichier" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 msgid "Push new repository" msgstr "Pusher le nouveau dépôt" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "Le dépôt existe déjà ?" @@ -5059,13 +5062,13 @@ msgstr "Révision fille" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "Statut du changeset" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "Diff brut" @@ -5075,7 +5078,7 @@ msgstr "Diff patch" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "Télécharger le diff" @@ -5102,8 +5105,8 @@ msgstr "Précédé par :" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -5111,8 +5114,8 @@ msgstr[1] "%s fichiers changés" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -5121,13 +5124,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "Afficher le diff complet quand même" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 msgid "No revisions" msgstr "Aucune révision" @@ -5143,106 +5146,87 @@ msgid "on this changeset" msgstr "sur ce changeset" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 msgid "Delete comment?" msgstr "Supprimer le commentaire ?" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 msgid "Status change" msgstr "Changement de statut" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." -msgstr "Commentaire sur la ligne {1}." +msgid "Commenting on line." +msgstr "Création d’un commentaire sur la ligne." #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, 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." - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." msgstr "" -"Utilisez @nomutilisateur dans ce texte pour envoyer une notification à un " -"autre utilisateur" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "Aperçu du commentaire" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +"Les commentaires sont du texte brut. Utilisez @nomutilisateur dans ce texte " +"pour envoyer une notification à un autre utilisateur." + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "Modifier le statut du changeset" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "Voter pour le statut de la requête de pull" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "Aucun changement" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +msgid "Finish pull request" +msgstr "Terminer la requête de pull" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "Fermer" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "Envoi…" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "Commentaire" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "Aperçu" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "Vous devez être connecté pour poster des commentaires." -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "Se connecter maintenant" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "Masquer" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "%d commentaire" msgstr[1] "%d commentaires" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "%d de ligne" msgstr[1] "%d de ligne" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "%d général" msgstr[1] "%d généraux" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" -"Utilisez @nomutilisateur dans ce texte pour envoyer une notification à un " -"autre utilisateur." - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "Voter pour le statut de la requête de pull" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -msgid "Set changeset status" -msgstr "Modifier le statut du changeset" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "Aucun changement" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -msgid "Close" -msgstr "Fermer" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5252,29 +5236,28 @@ msgid "Files affected" msgstr "Fichiers affectés" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "Supprimé" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "Renommé" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "Afficher le diff complet pour ce fichier" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "Afficher le diff complet côte-à-côte pour ce fichier" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "Afficher les commentaires de ligne" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "Supprimé" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "Renommé" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "Aucun changeset" @@ -5283,6 +5266,11 @@ msgid "Ancestor" msgstr "Ancêtre" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "Statut de changeset : %s" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "Premier changeset dans cette liste (le plus vieux)" @@ -5295,29 +5283,29 @@ msgid "Position in this list of changesets" msgstr "Position dans cette liste de changesets" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "Afficher le diff de fusion" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "Ancêtre commun" -#: kallithea/templates/compare/compare_cs.html:90 +#: kallithea/templates/compare/compare_cs.html:99 msgid "No common ancestor found - repositories are unrelated" msgstr "Aucun ancêtre commun trouvé - les dépôts n'ont aucun lien entre eux" -#: kallithea/templates/compare/compare_cs.html:98 +#: kallithea/templates/compare/compare_cs.html:107 msgid "is" msgstr "est" -#: kallithea/templates/compare/compare_cs.html:99 +#: kallithea/templates/compare/compare_cs.html:108 #, python-format msgid "%s changesets" msgstr "Changesets de %s" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "derrière" @@ -5328,28 +5316,28 @@ msgstr "Comparaison de %s" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "Comparer les révisions" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "Échanger" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "Comparer les révisions, les branches, les marque-pages ou les tags." -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "Affichage de %s commit" msgstr[1] "Affichage de %s commits" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "Afficher le diff complet" @@ -5410,19 +5398,27 @@ "Nous avons reçu une demande de réinitialisation du mot de passe de votre " "compte." -#: kallithea/templates/email_templates/password_reset.html:7 +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." +msgstr "" +"Cependant, ce compte est géré hors de ce système et le mot de passe ne peut " +"pas être changé ici." + +#: kallithea/templates/email_templates/password_reset.html:10 msgid "To set a new password, click the following link" msgstr "Pour choisir un nouveau mot de passe, cliquez sur le lien suivant" -#: kallithea/templates/email_templates/password_reset.html:10 +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -"Si vous ne pouvez pas utiliser le lien ci-dessus, merci de saisir le code " -"suivant dans le formulaire de réinitialisation de mot de passe" - -#: kallithea/templates/email_templates/password_reset.html:12 +"Si vous ne pouvez pas utiliser le lien ci-dessus, merci de saisir le code" +" suivant dans le formulaire de réinitialisation de mot de passe" + +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5507,8 +5503,8 @@ msgstr "Créer un nouveau fichier" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "Mode du nouveau fichier" +msgid "New file type" +msgstr "Nouveau type de fichier" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5628,23 +5624,31 @@ #: kallithea/templates/files/files_source.html:44 msgid "Editing files allowed only when on branch head revision" msgstr "" -"Édition de fichiers autorisée uniquement sur la révision de tête (head) de " -"la branche" +"Édition de fichiers autorisée uniquement sur la révision de tête (head) " +"de la branche" #: kallithea/templates/files/files_source.html:45 msgid "Deleting files allowed only when on branch head revision" msgstr "" -"Suppression de fichiers autorisée uniquement sur la révision de tête (head) " -"de la branche" +"Suppression de fichiers autorisée uniquement sur la révision de tête " +"(head) de la branche" #: kallithea/templates/files/files_source.html:63 #, python-format msgid "Binary file (%s)" msgstr "Fichier binaire (%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" -msgstr "Ce fichier est trop gros pour être affiché" +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "Ce fichier est trop gros pour être affiché." + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "Afficher les annotations complètes quand même." + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." +msgstr "Afficher en tant que texte brut." #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" @@ -5897,8 +5901,8 @@ "This is just a range of changesets and doesn't have a target or a real " "merge ancestor." msgstr "" -"Ceci est juste une série de changesets, et n'a pas de cible ou de véritable " -"ancêtre de fusion." +"Ceci est juste une série de changesets, et n'a pas de cible ou de " +"véritable ancêtre de fusion." #: kallithea/templates/pullrequests/pullrequest_show.html:133 msgid "Pull changes" @@ -5912,39 +5916,49 @@ msgid "Current revision - no change" msgstr "Révision courante - aucun changement" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" +"Les requêtes de pull ne peuvent pas changer après leur création. " +"Sélectionner une révision puis sauvegarder afin de remplacer cette requête " +"de pull par une nouvelle." + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "Relecteurs de la requête de pull" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 msgid "Remove reviewer" msgstr "Supprimer le relecteur" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "Saisir le nom du relecteur à ajouter" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -msgid "Potential Reviewers" -msgstr "Relecteurs potentiels" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "Saisir le nom du relecteur à ajouter" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +msgid "Potential Reviewers" +msgstr "Relecteurs potentiels" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "Cliquer pour ajouter le propriétaire du dépôt comme relecteur :" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "Enregistrer les changements" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "Sauvegarder en tant que nouvelle requête de pull" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" +"Sauvegarder les modifications entrantes en tant que nouvelle requête de pull" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 msgid "Cancel Changes" msgstr "Annuler les modifications" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "Contenu de la requête de pull" @@ -5955,8 +5969,8 @@ #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 #, python-format -msgid "Pull Requests from %s'" -msgstr "Requête de pull depuis %s'" +msgid "Pull Requests from '%s'" +msgstr "Requêtes de pull depuis '%s'" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 #, python-format @@ -5988,7 +6002,8 @@ #: kallithea/templates/pullrequests/pullrequest_show_my.html:30 msgid "Show closed pull requests (in addition to open pull requests)" msgstr "" -"Afficher les requêtes de pull fermées (en plus des requêtes de pull ouvertes)" +"Afficher les requêtes de pull fermées (en plus des requêtes de pull " +"ouvertes)" #: kallithea/templates/pullrequests/pullrequest_show_my.html:35 msgid "Pull Requests Created by Me" @@ -6422,30 +6437,6 @@ #~ msgid "reviewer" #~ msgstr "" -#~ 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" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "Votre nouveau mot de passe" @@ -6469,3 +6460,24 @@ #~ msgid "Created by" #~ msgstr "créé" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "Voulez-vous vraiment invalider le cache du dépôt ?" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "Aperçu du commentaire" + +#~ msgid "Preview" +#~ msgstr "Aperçu" + +#~ msgid "New file mode" +#~ msgstr "Mode du nouveau fichier" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/how_to --- a/kallithea/i18n/how_to Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/how_to Sat Dec 24 00:34:38 2016 +0100 @@ -94,4 +94,4 @@ Run Kallithea tests by executing:: - nosetests + py.test diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/hu/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/hu/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/hu/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2015-04-11 00:59+0200\n" "Last-Translator: Balázs Úr \n" "Language-Team: Hungarian " @@ -19,12 +19,12 @@ "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.3-dev\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -36,35 +36,29 @@ msgid "None" msgstr "" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "" @@ -116,10 +110,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "" @@ -128,111 +122,111 @@ msgid "%s committed on %s" msgstr "" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "" -#: kallithea/controllers/files.py:537 -#, python-format -msgid "Unknown revision %s" -msgstr "" - #: kallithea/controllers/files.py:539 -msgid "Empty repository" +#, python-format +msgid "Unknown revision %s" msgstr "" #: kallithea/controllers/files.py:541 +msgid "Empty repository" +msgstr "" + +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "" @@ -246,7 +240,7 @@ msgid "Groups" msgstr "" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -254,23 +248,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "" @@ -281,158 +279,163 @@ msgstr "" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "" - -#: kallithea/controllers/login.py:202 +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" msgstr "" -#: kallithea/controllers/login.py:251 +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" msgstr "" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "" +#: kallithea/controllers/pullrequests.py:172 +msgid "Special" +msgstr "" + #: kallithea/controllers/pullrequests.py:173 -msgid "Special" -msgstr "" - -#: kallithea/controllers/pullrequests.py:174 msgid "Peer branches" msgstr "" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 -msgid "No permission to change pull request status" -msgstr "" - #: kallithea/controllers/pullrequests.py:727 +msgid "No permission to change pull request status" +msgstr "" + +#: kallithea/controllers/pullrequests.py:738 +#, python-format +msgid "Successfully deleted pull request %s" +msgstr "" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "" @@ -448,12 +451,12 @@ msgid "An error occurred during search operation." msgstr "" -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "" @@ -474,64 +477,64 @@ msgid "Error occurred during update of defaults" msgstr "" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "" + +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 #: kallithea/controllers/admin/users.py:285 -msgid "Forever" +msgid "5 minutes" msgstr "" #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" +msgid "1 hour" msgstr "" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" +msgid "1 day" msgstr "" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "" @@ -546,7 +549,7 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "" @@ -556,33 +559,33 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -632,10 +635,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "" @@ -666,7 +669,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "" @@ -678,7 +681,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "" @@ -699,244 +702,244 @@ msgid "Error occurred during update of permissions" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "Ennek a tárolónak %s elágazása van" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "Ennek a tárolónak %s elágazása van" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "" @@ -977,76 +980,80 @@ msgstr "" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "" @@ -1062,125 +1069,125 @@ msgid "No changes detected" msgstr "" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "" -#: kallithea/lib/helpers.py:696 -msgid "and" -msgstr "" - #: kallithea/lib/helpers.py:697 +msgid "and" +msgstr "" + +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "" @@ -1190,8 +1197,8 @@ msgstr "" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "" @@ -1215,7 +1222,7 @@ msgid "chmod" msgstr "" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1223,69 +1230,69 @@ "repositories" msgstr "" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "" @@ -1384,7 +1391,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "" @@ -1495,7 +1502,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "" @@ -1510,7 +1517,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "" @@ -1537,7 +1544,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "" @@ -1684,7 +1691,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "" @@ -1711,12 +1718,12 @@ msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1725,104 +1732,104 @@ msgid "on line %s" msgstr "" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 -msgid "Default user has read access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1669 -msgid "Default user has write access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" +msgid "Default user has read access to new repositories" msgstr "" #: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" +msgid "Default user has write access to new repositories" msgstr "" #: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" msgstr "" #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -msgid "Only admins can create repository groups" +msgid "Default user has read access to new user groups" msgstr "" #: kallithea/model/db.py:1683 -msgid "Non-admins can create repository groups" -msgstr "" - -#: kallithea/model/db.py:1685 -msgid "Only admins can create user groups" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" msgstr "" #: kallithea/model/db.py:1686 -msgid "Non-admins can create user groups" -msgstr "" - -#: kallithea/model/db.py:1688 -msgid "Only admins can create top level repositories" +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" msgstr "" #: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 -msgid "Only admins can fork repositories" -msgstr "" - -#: kallithea/model/db.py:1695 -msgid "Non-admins can can fork repositories" -msgstr "" - #: kallithea/model/db.py:1698 -msgid "User registration with manual account activation" +msgid "Only admins can fork repositories" msgstr "" #: kallithea/model/db.py:1699 +msgid "Non-admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1702 +msgid "User registration with manual account activation" +msgstr "" + +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "" @@ -1845,7 +1852,7 @@ msgid "Enter %(min)i characters or more" msgstr "" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1938,7 +1945,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "" @@ -1971,15 +1978,15 @@ "owners or remove those user groups: %s" msgstr "" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -1990,167 +1997,167 @@ msgid "Value cannot be an empty list" msgstr "" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "" -#: kallithea/model/validators.py:153 -#, python-format -msgid "User group \"%(usergroup)s\" already exists" -msgstr "" - #: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" msgstr "" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 msgid "Invalid repository URL" msgstr "" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" msgstr "" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2277,7 +2284,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "" @@ -2325,7 +2332,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2335,7 +2342,7 @@ msgstr "" #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "" @@ -2350,7 +2357,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "" @@ -2358,7 +2365,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "" @@ -2370,7 +2377,7 @@ msgid "Forgot your password ?" msgstr "" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "" @@ -2491,10 +2498,6 @@ msgid "There are no branches yet" msgstr "" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2721,12 +2724,12 @@ msgid "Never" msgstr "" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "" @@ -2749,7 +2752,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2837,7 +2840,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2857,8 +2861,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2885,13 +2887,12 @@ msgstr "" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "" @@ -3072,7 +3073,7 @@ msgstr "" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "" @@ -3090,7 +3091,7 @@ msgstr "" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "" @@ -3288,7 +3289,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3551,6 +3552,11 @@ msgid "Unlock Repository" msgstr "" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "" @@ -3607,10 +3613,6 @@ msgid "Invalidate Repository Cache" msgstr "" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -4344,21 +4346,17 @@ msgid "Files" msgstr "" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4368,111 +4366,116 @@ msgid "Compare" msgstr "" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4589,6 +4592,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "" @@ -4697,51 +4701,53 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, python-format +msgid "Changeset status: %s by %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "" @@ -4757,7 +4763,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4787,21 +4793,21 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 msgid "Push new repository" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "" @@ -4819,13 +4825,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -4835,7 +4841,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -4862,8 +4868,8 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -4871,8 +4877,8 @@ msgstr[1] "" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4881,13 +4887,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 msgid "No revisions" msgstr "" @@ -4904,101 +4910,86 @@ msgid "on this changeset" msgstr "%s módosításcsomag" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 msgid "Delete comment?" msgstr "Hozzászólás törlése?" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "%s módosításcsomag" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +msgid "Commenting on line." msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +msgid "Finish pull request" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "%d sorközi" msgstr[1] "%d sorközi" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -msgid "Set changeset status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -msgid "Close" -msgstr "" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5008,29 +4999,28 @@ msgid "Files affected" msgstr "" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "" @@ -5039,6 +5029,11 @@ msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5051,29 +5046,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 #, python-format msgid "%s changesets" msgstr "%s módosításcsomag" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "" @@ -5084,28 +5079,28 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5164,17 +5159,23 @@ msgid "We have received a request to reset the password for your account." msgstr "" -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5257,7 +5258,7 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" +msgid "New file type" msgstr "" #: kallithea/templates/files/files_add.html:64 @@ -5388,8 +5389,16 @@ msgid "Binary file (%s)" msgstr "" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "" + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." msgstr "" #: kallithea/templates/files/files_ypjax.html:5 @@ -5652,39 +5661,45 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 msgid "Remove reviewer" msgstr "Átnéző eltávolítása" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 +#: kallithea/templates/pullrequests/pullrequest_show.html:261 msgid "Type name of reviewer to add" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:258 +#: kallithea/templates/pullrequests/pullrequest_show.html:269 msgid "Potential Reviewers" msgstr "Lehetséges átnézők" -#: kallithea/templates/pullrequests/pullrequest_show.html:261 +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 -msgid "Cancel Changes" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 +msgid "Cancel Changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "" @@ -5695,7 +5710,7 @@ #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 #, python-format -msgid "Pull Requests from %s'" +msgid "Pull Requests from '%s'" msgstr "" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 @@ -6271,39 +6286,12 @@ #~ msgid "Your password reset link was sent" #~ msgstr "" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "" #~ msgid "Your new Kallithea password:%s" #~ msgstr "" -#~ msgid "" -#~ "Password reset link will be sent " -#~ "to the email address matching your " -#~ "username." -#~ msgstr "" - #~ msgid "Open New Pull Request for Selected Changesets" #~ msgstr "" @@ -6325,3 +6313,53 @@ #~ msgid "Created by" #~ msgstr "" +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "You can only edit files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Non-admins can can fork repositories" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "" + +#~ msgid "Commenting on line {1}." +#~ msgstr "" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "" + +#~ msgid "Preview" +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user." +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "File is too big to display" +#~ msgstr "" + +#~ msgid "Save as New Pull Request" +#~ msgstr "" + +#~ msgid "Pull Requests from %s'" +#~ msgstr "" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/ja/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/ja/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/ja/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -12,11 +12,11 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2016-01-07 01:53+0000\n" "Last-Translator: Takumi IINO \n" "Language-Team: Japanese " -"\n" +"\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -24,12 +24,12 @@ "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 2.5-dev\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "まだチェンジセットがありません" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -41,35 +41,29 @@ msgid "None" msgstr "なし" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(閉鎖済み)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "空白を表示" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "空白を無視" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "diff コンテキストを %(num)s 行増やす" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "お探しのリビジョンはこのリポジトリにはありません" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "クローズしたプルリクエストに関連するチェンジセットのステータスを変更することは許可されていません" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "リビジョンを選択" @@ -121,10 +115,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "チェンジセットが大きすぎるため、省略しました..." @@ -133,111 +127,113 @@ msgid "%s committed on %s" msgstr "%s が %s にコミット" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "新しいファイルを追加" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "まだファイルがありません。 %s" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, fuzzy, python-format msgid "%s at %s" msgstr "%s と %s の間" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "このリポジトリは %s によって %s にロックされました" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " +#: kallithea/controllers/files.py:319 +#, fuzzy +msgid "You can only delete files with revision being a valid branch" msgstr "有効なブランチ上のリビジョンからしかファイルを削除できません" -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "Kallithea経由で %s を削除" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "%s ファイルの削除に成功しました" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "コミット中にエラーが発生しました" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " +#: kallithea/controllers/files.py:379 +#, fuzzy +msgid "You can only edit files with revision being a valid branch" msgstr "有効なブランチを示すリビジョンでのみファイルを編集できます " -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "Kallithea経由で %s を変更" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "変更点なし" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "%s へのコミットが成功しました" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Kallithea経由でファイルを追加" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "内容がありません" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "ファイル名がありません" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "場所には相対パスかつ .. を含まないパスを入力してください" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "ダウンロードは無効化されています" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "%s は未知のリビジョンです" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "空のリポジトリ" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "未知のアーカイブ種別です" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "チェンジセット" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "ブランチ" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "タグ" @@ -251,7 +247,7 @@ msgid "Groups" msgstr "グループ" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -259,23 +255,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "リポジトリ" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "ブランチ" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "閉鎖済みブランチ" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "タグ" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "ブックマーク" @@ -286,161 +286,164 @@ msgstr "公開ジャーナル" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "ジャーナル" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "キャプチャが一致しません" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "Kallitheaへの登録を受け付けました" - -#: kallithea/controllers/login.py:202 -#| msgid "Your password reset link was sent" +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "%sへの登録を受け付けました" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" msgstr "パスワードリセットの確認コードが送信されました" -#: kallithea/controllers/login.py:251 -#| msgid "Password reset link" +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" msgstr "無効なパスワードリセットトークン" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "パスワードを更新しました" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (閉鎖済み)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "チェンジセット" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "スペシャル" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "相手のブランチ" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "ブックマーク" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "プルリクエスト作成中にエラーが発生しました: %s" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "説明がありません" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "新しいプルリクエストの作成に成功しました" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "プルリクエストの作成中にエラーが発生しました" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "%s で置き換えられたのでクローズします。" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 #, fuzzy msgid "Pull request update created" msgstr "プルリクエストレビュアー" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "プルリクエストを更新しました" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "プルリクエストの削除に成功しました" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "このプルリクエストはすでにクローズされていて、更新することはできません。" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" +#: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" msgstr "" -#: kallithea/controllers/pullrequests.py:617 +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "プルリクエストを更新するためのチェンジセットが見つかりません。" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "ノート: ブランチ%sには別のヘッド%sがあります。" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "Gitのプルリクエストはまだ更新をサポートしていません。" -#: kallithea/controllers/pullrequests.py:722 +#: kallithea/controllers/pullrequests.py:727 msgid "No permission to change pull request status" msgstr "プルリクエストステータスを変更する権限がありません" -#: kallithea/controllers/pullrequests.py:727 +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "プルリクエストの削除に成功しました" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "クローズ。" @@ -456,12 +459,12 @@ msgid "An error occurred during search operation." msgstr "検索を実行する際にエラーが発生しました。" -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "まだデータの準備ができていません" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "このリポジトリの統計は無効化されています" @@ -482,64 +485,64 @@ msgid "Error occurred during update of defaults" msgstr "デフォルト設定の更新中にエラーが発生しました" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 -#: kallithea/controllers/admin/users.py:285 +#: kallithea/controllers/admin/users.py:284 msgid "Forever" msgstr "永久" +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/users.py:285 +msgid "5 minutes" +msgstr "5 分" + #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" -msgstr "5 分" +msgid "1 hour" +msgstr "1 時間" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "1 時間" +msgid "1 day" +msgstr "1 日" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "1 日" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "1 ヶ月" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "有効期間" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "gist の作成中にエラーが発生しました" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "gist %s を削除しました" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "変更しない" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "Gist の内容を更新しました" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "Gist データを更新しました" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "Gist %s の更新中にエラーが発生しました" @@ -554,7 +557,7 @@ msgstr "アカウントの更新に成功しました" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "ユーザー %s の更新中にエラーが発生しました" @@ -564,33 +567,33 @@ msgstr "パスワードの更新中にエラーが発生しました" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "ユーザーにメールアドレス %s を追加しました" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "メールの保存時にエラーが発生しました" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "ユーザーからメールアドレスを削除しました" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "APIキーの作成に成功しました" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "APIキーのリセットに成功しました" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "APIキーの削除に成功しました" @@ -640,10 +643,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "管理" @@ -674,7 +677,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "外部アカウントを手動でアクティベートする" @@ -686,7 +689,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "外部アカウントを自動でアクティベートする" @@ -707,242 +710,242 @@ msgid "Error occurred during update of permissions" msgstr "権限の更新中にエラーが発生しました" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "リポジトリグループ %s の作成中にエラーが発生しました" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "リポジトリグループ %s を作成しました" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "リポジトリグループ %s を更新しました" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "リポジトリグループ %s の更新中にエラーが発生しました" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "このグループは %s 個のリポジトリを含んでいるため削除できません" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "このグループは %s 個のサブグループを含んでいるため削除できません" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "リポジトリグループ %s を削除しました" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "リポジトリグループ %s の削除中にエラーが発生しました" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "自分自身の管理者としての権限を取り消すことはできません" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "リポジトリグループ権限を更新しました" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "権限の取消中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "リポジトリ %s の作成中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "リポジトリ %s を %s から作成しました" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "リポジトリ %s を %s としてフォークしました" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "リポジトリ %s を作成しました" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "リポジトリ %s の更新に成功しました" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "リポジトリ %s の更新中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "%s 個のフォークを切り離しました" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "%s 個のフォークを削除しました" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "リポジトリ %s を削除しました" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "フォークしたリポジトリが存在するため、 リポジトリ %s は削除できません" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "%s の削除中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "リポジトリ権限を更新しました" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "フィールドの作成中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "フィールドの削除中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "-- フォークではありません --" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "公開ジャーナルでのリポジトリの可視性を更新しました" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "このリポジトリの公開ジャーナルの設定中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "ありません" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "%s リポジトリを %s のフォークとする" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "操作中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 msgid "Repository has been locked" msgstr "リポジトリがロックされました" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 msgid "Repository has been unlocked" msgstr "リポジトリのロックが解除されました" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "アンロック中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "キャッシュの無効化に成功しました" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "キャッシュの無効化中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "リモートから取得" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "リモートから取得中にエラーが発生しました" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "リポジトリステートの削除中にエラーが発生しました" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "VCS設定を更新しました" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "\"hgsubversion\"ライブラリが見つからないため、hgsubversionサポートを有効に出来ません" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "アプリケーション設定の更新中にエラーが発生しました" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "リポジトリの再スキャンに成功しました。 追加: %s 削除: %s。" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "アプリケーション設定を更新しました" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "表示設定を更新しました" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "表示設定の更新中にエラーが発生しました" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "メールアドレスを入力してください" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "メール送信タスクを作成しました" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "新しいフックを追加しました" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "フックを更新しました" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "フックの作成中にエラーが発生しました" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "Whooshの再インデックスタスクを予定に入れました" @@ -983,76 +986,80 @@ msgstr "ユーザーグループ権限を更新しました" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "権限を更新しました" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "権限の保存時にエラーが発生しました" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "ユーザー %s を作成しました" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "ユーザー %s の作成中にエラーが発生しました" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "ユーザーの更新に成功しました" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "ユーザーの削除に成功しました" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "ユーザーの削除中にエラーが発生しました" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "デフォルト ユーザーを編集できません" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "ユーザーホワイトリストにIP %s を追加しました" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "IPアドレスの保存中にエラーが発生しました" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "ユーザーホワイトリストからIPアドレスを削除しました" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "IPアドレス %s は許可されません" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "APIキーが無効です" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "このアクションを実行するためには登録済みのユーザーである必要があります" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "このページを閲覧するためにはサインインが必要です" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "ファイルシステム内にリポジトリが見つかりません" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "リビジョンが見つかりません" @@ -1068,125 +1075,125 @@ msgid "No changes detected" msgstr "検出された変更はありません" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "削除されたブランチ: %s" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "作成したタグ: %s" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "%s から %s までのすべてのチェンジセットを表示" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "比較ビュー" -#: kallithea/lib/helpers.py:696 +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "と" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "%s 以上" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "リビジョン" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "フォーク名 %s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "プルリクエスト #%s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "リポジトリを[削除]" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "リポジトリを[作成]" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "フォークしてリポジトリを[作成]" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "リポジトリを[フォーク]" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "リポジトリを[更新]" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "リポジトリからアーカイブを[ダウンロード]" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "リポジトリを[削除]" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "ユーザーを[作成]" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "ユーザーを[更新]" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "ユーザーグループを[作成]" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "ユーザーグループを[更新]" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "リポジトリのリビジョンに[コメント]" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "プルリクエストに[コメント]" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "プルリクエストを[クローズ]" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[プッシュ]" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "リポジトリに[Kallithea経由でコミット]" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "リポジトリに[リモートからプル]" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "[プル]" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "リポジトリの[フォローを開始]" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "リポジトリの[フォローを停止]" @@ -1196,8 +1203,8 @@ msgstr " と %s 以上" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "ファイルはありません" @@ -1221,73 +1228,73 @@ msgid "chmod" msgstr "chmod" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, 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内に見つかりませんでした。おそらくファイルシステム上で作られたか名前が変更されたためです。リポジトリをもう一度チェックするためにアプ" -"リケーションを再起動してください" - -#: kallithea/lib/utils2.py:415 +"%s " +"リポジトリはDB内に見つかりませんでした。おそらくファイルシステム上で作られたか名前が変更されたためです。リポジトリをもう一度チェックするためにアプリケーションを再起動してください" + +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "%d 年" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d ヶ月" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d 日" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d 時間" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d 分" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "%d 秒" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "%s 以内" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "%s 前" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "%s と %s の間" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "%s と %s 前" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "たったいま" @@ -1386,7 +1393,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Kallithea 管理者" @@ -1497,7 +1504,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "承認" @@ -1512,7 +1519,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "却下" @@ -1539,7 +1546,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "top level" @@ -1686,7 +1693,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "新規登録を無効にする" @@ -1713,12 +1720,12 @@ msgstr "ユーザーの新規登録時に自動でアカウントをアクティベートする" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "リポジトリグループの書き込みパーミッションを使ったリポジトリ作成が有効です" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "リポジトリグループの書き込みパーミッションを使ったリポジトリ作成は無効です" @@ -1727,103 +1734,104 @@ msgid "on line %s" msgstr "%s 行目" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[Mention]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "デフォルトユーザーは新しいリポジトリにアクセスできません" -#: kallithea/model/db.py:1668 +#: kallithea/model/db.py:1672 msgid "Default user has read access to new repositories" msgstr "デフォルトユーザーは新しいリポジトリに読み取りアクセスする権限があります" -#: kallithea/model/db.py:1669 +#: kallithea/model/db.py:1673 msgid "Default user has write access to new repositories" msgstr "デフォルトユーザーは新しいリポジトリに書き込みアクセスする権限があります" -#: kallithea/model/db.py:1670 +#: kallithea/model/db.py:1674 msgid "Default user has admin access to new repositories" msgstr "" -#: kallithea/model/db.py:1672 +#: kallithea/model/db.py:1676 msgid "Default user has no access to new repository groups" msgstr "" -#: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" -msgstr "" - #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" +msgid "Default user has admin access to new repository groups" msgstr "" -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -msgid "Only admins can create repository groups" -msgstr "管理者のみがリポジトリのグループを作成できます" +msgid "Default user has read access to new user groups" +msgstr "" #: kallithea/model/db.py:1683 +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1686 +msgid "Only admins can create repository groups" +msgstr "管理者のみがリポジトリのグループを作成できます" + +#: kallithea/model/db.py:1687 msgid "Non-admins can create repository groups" msgstr "非管理者がリポジトリのグループを作成できます" -#: kallithea/model/db.py:1685 +#: kallithea/model/db.py:1689 msgid "Only admins can create user groups" msgstr "管理者だけがユーザー グループを作成することができます" -#: kallithea/model/db.py:1686 +#: kallithea/model/db.py:1690 msgid "Non-admins can create user groups" msgstr "非管理者ユーザーがグループを作成することができます" -#: kallithea/model/db.py:1688 +#: kallithea/model/db.py:1692 msgid "Only admins can create top level repositories" msgstr "管理者だけがトップレベルにリポジトリを作成することができます" -#: kallithea/model/db.py:1689 +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "非管理者がトップレベルにリポジトリを作成することができます" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 msgid "Only admins can fork repositories" msgstr "管理者のみがリポジトリをフォークすることができます" -#: kallithea/model/db.py:1695 -msgid "Non-admins can can fork repositories" +#: kallithea/model/db.py:1699 +#, fuzzy +msgid "Non-admins can fork repositories" msgstr "非管理者がリポジトリをフォークすることができます" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 msgid "User registration with manual account activation" msgstr "ユーザーの新規登録時に手動でアカウントをアクティベートする" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "ユーザーの新規登録時に自動でアカウントをアクティベートする" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 msgid "Not reviewed" msgstr "未レビュー" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 msgid "Under review" msgstr "レビュー中" @@ -1845,7 +1853,7 @@ msgid "Enter %(min)i characters or more" msgstr "%(min)i 文字以上必要です" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "数字だけの名前は使えません" @@ -1921,13 +1929,11 @@ #: kallithea/model/notification.py:307 #, fuzzy, python-format -#| msgid "%(user)s wants you to review pull request %(pr_nice_id)s:" msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "%(user)s がプリリクエスト #%(pr_id)s: %(pr_title)s のレビューを求めています" #: kallithea/model/notification.py:308 #, fuzzy, python-format -#| msgid "[commented] on pull request for" msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "プルリクエストに[コメント]" @@ -1940,7 +1946,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "%(user)s がプリリクエスト #%(pr_nice_id)s: %(pr_title)s のレビューを求めています" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "最新のtip" @@ -1958,8 +1964,7 @@ msgid "" "User \"%s\" still owns %s repositories and cannot be removed. Switch " "owners or remove those repositories: %s" -msgstr "" -"ユーザー \"%s\" はまだ %s 個のリポジトリの所有者のため削除することはできません。リポジトリの所有者を変更するか削除してください: %s" +msgstr "ユーザー \"%s\" はまだ %s 個のリポジトリの所有者のため削除することはできません。リポジトリの所有者を変更するか削除してください: %s" #: kallithea/model/user.py:266 #, python-format @@ -1976,19 +1981,18 @@ "User \"%s\" still owns %s user groups and cannot be removed. Switch " "owners or remove those user groups: %s" msgstr "" -"ユーザー \"%s\" はまだ %s 個のユーザーグループの所有者のため削除することはできません。ユーザーグループの所有者を変更するか削除してください。 " -"%s" - -#: kallithea/model/user.py:360 +"ユーザー \"%s\" はまだ %s " +"個のユーザーグループの所有者のため削除することはできません。ユーザーグループの所有者を変更するか削除してください。 %s" + +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "パスワードリセットのリンク" -#: kallithea/model/user.py:408 -#| msgid "Password reset link" +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "パスワードの再設定通知" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -1999,170 +2003,167 @@ msgid "Value cannot be an empty list" msgstr "空のリストにはできません" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "ユーザー名 \"%(username)s\" はすでに使われています" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "ユーザー名 %(username)s は使用できません" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" -msgstr "" -"ユーザー名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットまたはアンダースコア(_)から始まる必" -"要があります" - -#: kallithea/model/validators.py:126 +msgstr "ユーザー名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットまたはアンダースコア(_)から始まる必要があります" + +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "入力が正しくありません" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "ユーザー名 %(username)s は不正です" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "不正なユーザーグループ名です" -#: kallithea/model/validators.py:153 +#: kallithea/model/validators.py:155 #, python-format msgid "User group \"%(usergroup)s\" already exists" msgstr "ユーザーグループ \"%(usergroup)s\" はすでに存在します" -#: kallithea/model/validators.py:155 +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" -msgstr "" -"ユーザーグループ名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットから始まる必要があります" - -#: kallithea/model/validators.py:193 +msgstr "ユーザーグループ名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットから始まる必要があります" + +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "このグループは親にできません" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "グループ \"%(group_name)s\" はすでに存在します" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "グループ名 \"%(group_name)s\" を持つリポジトリはすでに存在します" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "パスワードに利用出来ない文字列(non-ascii)です" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "古いpasswordが間違っています" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "パスワードが一致しません" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "ユーザー名とパスワードの組み合わせが無効です" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "トークンが一致しません" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "リポジトリ名 %(repo)s は許可されていません" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "リポジトリ %(repo)s はすでに存在します" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "リポジトリ \"%(repo)s\" は グループ \"%(group)s\" にすでに存在します" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "リポジトリグループ名 \"%(repo)s\" はすでに存在します" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 msgid "Invalid repository URL" msgstr "無効なリポジトリのURL" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "フォークは親と同じ種別の必要があります" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "このグループにリポジトリを作成する権限がありません" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "ルートにリポジトリを作成する権限がありません" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "この場所にグループを作成する権限がありません" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "ユーザー名かユーザーグループが不正です" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "不正なパスです" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" msgstr "このメールアドレスはすでに取得されています" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "メールアドレス \"%(email)s\" がみつかりません" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "LDAPのこのCNに対するログイン属性は必須です。 - これは \"ユーザー名\" と同じです" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "有効なIPv4かIPv6のアドレスを入力してください" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "ネットワークサイズ (bits) は0-32の範囲にする必要があります ( %(bits)r は不正です)" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "キー名にはアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)、数字が使えます" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "ファイル名はディレクトリ内にすることはできません" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "%(loaded)s プラグインと %(next_to_load)s プラグインで同じ名前が使われています" @@ -2289,7 +2290,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "所有者" @@ -2337,7 +2338,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2347,7 +2348,7 @@ msgstr "読み込み中..." #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "ログイン" @@ -2362,7 +2363,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "ユーザー名" @@ -2370,7 +2371,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "パスワード" @@ -2382,7 +2383,7 @@ msgid "Forgot your password ?" msgstr "パスワードを忘れた?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "アカウントを持っていない?" @@ -2420,7 +2421,6 @@ msgstr "パスワードリセットメールを送信" #: kallithea/templates/password_reset.html:47 -#| msgid "" " msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." @@ -2442,12 +2442,10 @@ msgstr "" #: kallithea/templates/password_reset_confirmation.html:39 -#| msgid "New password" msgid "New Password" msgstr "新しいパスワード" #: kallithea/templates/password_reset_confirmation.html:48 -#| msgid "Confirm new password" msgid "Confirm New Password" msgstr "新しいパスワードの確認" @@ -2506,10 +2504,6 @@ msgid "There are no branches yet" msgstr "まだブランチがありません" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "閉鎖済みブランチ" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2737,12 +2731,12 @@ msgid "Never" msgstr "しない" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "Gistを更新" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "キャンセル" @@ -2765,7 +2759,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "新しい Gist を作成" @@ -2853,7 +2847,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2873,8 +2868,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2901,13 +2894,12 @@ msgstr "作成日" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "Raw形式で表示" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "アカウント" @@ -3088,7 +3080,7 @@ msgstr "コメント" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "プルリクエスト" @@ -3106,7 +3098,7 @@ msgstr "通知を表示" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "通知" @@ -3309,7 +3301,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3571,6 +3563,11 @@ msgid "Unlock Repository" msgstr "リポジトリのロックを解除" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "このリポジトリをロックしますか?" @@ -3626,10 +3623,6 @@ msgid "Invalidate Repository Cache" msgstr "リポジトリのキャッシュを無効化" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "リポジトリのキャッシュを無効化してもよろしいですか?" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -4130,16 +4123,16 @@ msgstr "" "クローン URL のスキーマは、 '{scheme}://{user}@{netloc}/{repo}' " "のような形式にします。使える変数は下記の通りです:\n" -" {scheme} Kallithea " -"サーバからリクエストを送信するときに使うスキーム。 'http' または 'https'\n" +" {scheme} " +"Kallithea サーバからリクエストを送信するときに使うスキーム。 'http' または 'https'\n" " {user} " "現在のユーザーのユーザー名\n" -" {netloc} Kallithea " -"サーバーのアドレスまたはホスト名\n" +" {netloc} " +"Kallithea サーバーのアドレスまたはホスト名\n" " {repo} " "リポジトリの完全な名前\n" -" {repoid} リポジトリの ID。 " -"clone-by-id に使います。" +" {repoid} リポジトリの " +"ID。 clone-by-id に使います。" #: kallithea/templates/admin/settings/settings_visual.html:55 msgid "Dashboard items" @@ -4388,21 +4381,17 @@ msgid "Files" msgstr "ファイル" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "ブランチの切り替え" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "オプション" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "フォークと比較" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4412,111 +4401,116 @@ msgid "Compare" msgstr "比較" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "検索" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "アンロック" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "ロック" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "フォロー" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "アンフォロー" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "アンフォロー" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "フォーク" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "プルリクエストを作成" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "%s のプルリクエストを表示" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "ブランチの切り替え" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "一致するものが見つかりません" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "最近の活動を表示" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "公開ジャーナル" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "公開 gists を表示" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "Gists" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "すべての公開 Gists" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "公開 Gists" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "非公開 Gists" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "リポジトリから検索" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "私のプルリクエスト" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "ログインしていません" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "あなたのアカウントにログイン" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "パスワードを忘れた?" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "ログアウト" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "一致するものが見つかりません" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "キーボードショートカット" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "サイト全体" @@ -4616,7 +4610,6 @@ msgstr "マッチするファイルはありません" #: kallithea/templates/base/root.html:31 -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "新しいプルリクエストを{0}から作成" @@ -4625,7 +4618,6 @@ msgstr "{0} → {1}から新しいプルリクエストを作成する" #: kallithea/templates/base/root.html:33 -#| msgid "Show Selected Changesets __S → __E" msgid "Show Selected Changesets {0} → {1}" msgstr "選択したチェンジセット{0} → {0}を表示" @@ -4636,6 +4628,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "差分をたたむ" @@ -4741,55 +4734,59 @@ #: kallithea/templates/changelog/changelog.html:92 #: kallithea/templates/changelog/changelog_summary_data.html:20 -#, python-format +#, fuzzy, python-format +#| msgid "" "Changeset status: %s\n" "Click to open associated pull request %s" msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" "チェンジセットステータス: %s\n" "関連するプルリクエスト %s を開く" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Changeset status: %s" +msgid "Changeset status: %s by %s" msgstr "チェンジセットステータス: %s" -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "コミットメッセージを展開" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "チェンジセットにコメントがあります" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "ブックマーク %s" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "タグ %s" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "ブランチ %s" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "まだ変更がありません" @@ -4805,7 +4802,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "追加" @@ -4835,21 +4832,21 @@ msgid "Refs" msgstr "Refs" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "Kallithea経由で直接ファイルを追加またはアップロード" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "新しいファイルを追加" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 msgid "Push new repository" msgstr "新しいリポジトリをプッシュ" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "存在するリポジトリをプッシュ" @@ -4867,13 +4864,13 @@ msgstr "子リビジョン" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "チェンジセットステータス" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "diffとして差分を表示" @@ -4883,7 +4880,7 @@ msgstr "パッチとして差分を表示" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "差分をダウンロード" @@ -4913,16 +4910,16 @@ msgstr "作成日" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" msgstr[0] "%s ファイルに影響" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4930,13 +4927,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "とにかくすべての差分を表示" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 msgid "No revisions" msgstr "リビジョンなし" @@ -4954,98 +4951,86 @@ msgid "on this changeset" msgstr "チェンジセットはありません" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 msgid "Delete comment?" msgstr "コメントを削除しますか?" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 msgid "Status change" msgstr "ステータスを変更" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +#, fuzzy +msgid "Commenting on line." msgstr "{1} 行目にコメント" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "コメントには %s 構文 ( %s サポートつき ) が利用できます。" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "テキスト内で @username を使うと、そのユーザーに通知されます" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "コメントのプレビュー" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +#, fuzzy +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "テキスト内で @username を使うと、そのユーザーに通知されます。" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "リビジョンステータスを設定" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "プルリクエストステータスの投票" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "変更なし" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "プルリクエスト #%s にコメント" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +#, fuzzy +msgid "Close" +msgstr "(閉鎖済み)" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "送信中..." -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "コメント" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "プレビュー" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "コメントにはログインする必要があります。" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "今すぐログインする" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "隠す" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "%d 個のコメント" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "%d inline" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "%d general" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "テキスト内で @username を使うと、そのユーザーに通知されます。" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "プルリクエストステータスの投票" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -msgid "Set changeset status" -msgstr "リビジョンステータスを設定" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "変更なし" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -#, fuzzy -msgid "Close" -msgstr "(閉鎖済み)" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5055,29 +5040,28 @@ msgid "Files affected" msgstr "影響のあるファイル" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "削除" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "リネーム" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "このファイルのすべての差分を表示" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "このファイルの差分を並べて表示" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "インラインコメントを表示" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "削除" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "リネーム" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "チェンジセットはありません" @@ -5086,6 +5070,11 @@ msgid "Ancestor" msgstr "祖先" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "チェンジセットステータス: %s" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5098,29 +5087,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "マージの差分を表示" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "共通の祖先" -#: kallithea/templates/compare/compare_cs.html:90 +#: kallithea/templates/compare/compare_cs.html:99 msgid "No common ancestor found - repositories are unrelated" msgstr "共通の祖先が見つかりません - リポジトリ同士に関連がありません" -#: kallithea/templates/compare/compare_cs.html:98 +#: kallithea/templates/compare/compare_cs.html:107 msgid "is" msgstr "is" -#: kallithea/templates/compare/compare_cs.html:99 +#: kallithea/templates/compare/compare_cs.html:108 #, python-format msgid "%s changesets" msgstr "%s チェンジセット" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "behind" @@ -5131,27 +5120,27 @@ msgstr "%s 比較" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "リビジョンを比較" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "入れ替え" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "リビジョン、ブランチ、ブックマークもしくはタグの比較を行います。" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "%s コミットを表示" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "すべての差分を表示" @@ -5207,21 +5196,26 @@ msgstr "こんにちは %s" #: kallithea/templates/email_templates/password_reset.html:6 -#| msgid "We received a request to create a new password for your account." msgid "We have received a request to reset the password for your account." msgstr "あなたのアカウントのパスワードリセットリクエストを受け取りました。" -#: kallithea/templates/email_templates/password_reset.html:7 +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:10 msgid "To set a new password, click the following link" msgstr "新しいパスワードを設定するために、次のリンクをクリックしてください" -#: kallithea/templates/email_templates/password_reset.html:10 +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5306,8 +5300,9 @@ msgstr "新しいファイルを作成" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "ファイルモード" +#, fuzzy +msgid "New file type" +msgstr "新しいファイル" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5436,10 +5431,21 @@ msgid "Binary file (%s)" msgstr "バイナリファイル (%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +#, fuzzy +msgid "File is too big to display." msgstr "表示するには大きすぎるファイルです" +#: kallithea/templates/files/files_source.html:76 +#, fuzzy +msgid "Show full annotation anyway." +msgstr "とにかくすべての差分を表示" + +#: kallithea/templates/files/files_source.html:78 +#, fuzzy +msgid "Show as raw." +msgstr "Raw形式で表示" + #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" msgstr "アノテーション" @@ -5699,39 +5705,46 @@ msgid "Current revision - no change" msgstr "現在のリビジョン ー 変更なし" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "プルリクエストレビュアー" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 msgid "Remove reviewer" msgstr "レビュアーを削除" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "追加するレビュアーの名前を入力" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -msgid "Potential Reviewers" -msgstr "レビュワー候補" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "追加するレビュアーの名前を入力" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +msgid "Potential Reviewers" +msgstr "レビュワー候補" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "クリックしてリポジトリの所有所をレビュアーに追加:" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "変更を保存" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#, fuzzy +msgid "Save Updates as New Pull Request" msgstr "新しいプルリクエストとして保存" -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:297 msgid "Cancel Changes" msgstr "変更をキャンセル" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "プルリクエストの内容" @@ -5741,8 +5754,8 @@ msgstr "%s プルリクエスト" #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 -#, python-format -msgid "Pull Requests from %s'" +#, fuzzy, python-format +msgid "Pull Requests from '%s'" msgstr "%s' からのプルリクエスト" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 @@ -6016,7 +6029,7 @@ #~ msgstr "ファイルなし" #~ msgid "" -#~ msgstr "テキスト内で @username を使うと、その Kallithea のユーザーに通知を送信します" +#~ msgstr "クローズしたプルリクエストに関連するチェンジセットのステータスを変更することは許可されていません" #~ msgid "Username \"%(username)s\" is forbidden" #~ msgstr "ユーザー名 \"%(username)s\" は許可されていません" @@ -6186,27 +6199,6 @@ #~ msgid "reviewer" #~ msgstr "レビュアー" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "パスワードをリセットに成功しました。新しいパスワードをあなたのメールアドレスに送りました" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "新しいパスワード" @@ -6227,3 +6219,25 @@ #~ msgid "Created by" #~ msgstr "作成日" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "リポジトリのキャッシュを無効化してもよろしいですか?" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "コメントには %s 構文 ( %s サポートつき ) が利用できます。" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "テキスト内で @username を使うと、そのユーザーに通知されます" + +#~ msgid "Comment preview" +#~ msgstr "コメントのプレビュー" + +#~ msgid "Preview" +#~ msgstr "プレビュー" + +#~ msgid "New file mode" +#~ msgstr "ファイルモード" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/kallithea.pot --- a/kallithea/i18n/kallithea.pot Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/kallithea.pot Sat Dec 24 00:34:38 2016 +0100 @@ -1,14 +1,14 @@ # Translations template for Kallithea. -# Copyright (C) 2015 Various authors, licensing as GPLv3 +# Copyright (C) 2016 Various authors, licensing as GPLv3 # This file is distributed under the same license as the Kallithea project. -# FIRST AUTHOR , 2015. +# FIRST AUTHOR , 2016. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Kallithea 0.3\n" +"Project-Id-Version: Kallithea 0.3.99\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16,12 +16,12 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -33,33 +33,29 @@ msgid "None" msgstr "" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "" -#: kallithea/controllers/changeset.py:383 -msgid "Changing status on a changeset associated with a closed pull request is not allowed" -msgstr "" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "" @@ -109,10 +105,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "" @@ -121,111 +117,111 @@ msgid "%s committed on %s" msgstr "" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "" -#: kallithea/controllers/files.py:537 -#, python-format -msgid "Unknown revision %s" -msgstr "" - #: kallithea/controllers/files.py:539 -msgid "Empty repository" +#, python-format +msgid "Unknown revision %s" msgstr "" #: kallithea/controllers/files.py:541 +msgid "Empty repository" +msgstr "" + +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "" @@ -239,7 +235,7 @@ msgid "Groups" msgstr "" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -247,23 +243,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "" @@ -274,156 +274,161 @@ msgstr "" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "" - -#: kallithea/controllers/login.py:202 +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" msgstr "" -#: kallithea/controllers/login.py:251 +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" msgstr "" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 -#, python-format -msgid "This pull request is based on another %s revision and there is no simple diff." -msgstr "" - #: kallithea/controllers/pullrequests.py:424 #, python-format +msgid "This pull request is based on another %s revision and there is no simple diff." +msgstr "" + +#: kallithea/controllers/pullrequests.py:426 +#, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 -msgid "No permission to change pull request status" -msgstr "" - #: kallithea/controllers/pullrequests.py:727 +msgid "No permission to change pull request status" +msgstr "" + +#: kallithea/controllers/pullrequests.py:738 +#, python-format +msgid "Successfully deleted pull request %s" +msgstr "" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "" @@ -439,12 +444,12 @@ msgid "An error occurred during search operation." msgstr "" -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "" @@ -465,64 +470,64 @@ msgid "Error occurred during update of defaults" msgstr "" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "" + +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 #: kallithea/controllers/admin/users.py:285 -msgid "Forever" +msgid "5 minutes" msgstr "" #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" +msgid "1 hour" msgstr "" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" +msgid "1 day" msgstr "" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "" @@ -537,7 +542,7 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "" @@ -547,33 +552,33 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -623,10 +628,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "" @@ -657,7 +662,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "" @@ -669,7 +674,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "" @@ -690,240 +695,240 @@ msgid "Error occurred during update of permissions" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 msgid "Repository has been locked" msgstr "" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 msgid "Repository has been unlocked" msgstr "" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "Unable to activate hgsubversion support. The \"hgsubversion\" library is missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "" @@ -964,76 +969,80 @@ msgstr "" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "" @@ -1049,125 +1058,125 @@ msgid "No changes detected" msgstr "" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "" -#: kallithea/lib/helpers.py:696 -msgid "and" -msgstr "" - #: kallithea/lib/helpers.py:697 +msgid "and" +msgstr "" + +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "" @@ -1177,8 +1186,8 @@ msgstr "" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "" @@ -1202,74 +1211,74 @@ msgid "chmod" msgstr "" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories" msgstr "" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "" @@ -1368,7 +1377,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "" @@ -1479,7 +1488,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "" @@ -1494,7 +1503,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "" @@ -1521,7 +1530,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "" @@ -1668,7 +1677,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "" @@ -1695,12 +1704,12 @@ msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1709,103 +1718,103 @@ msgid "on line %s" msgstr "" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 -msgid "Default user has read access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1669 -msgid "Default user has write access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" +msgid "Default user has read access to new repositories" msgstr "" #: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" +msgid "Default user has write access to new repositories" msgstr "" #: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" msgstr "" #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -msgid "Only admins can create repository groups" +msgid "Default user has read access to new user groups" msgstr "" #: kallithea/model/db.py:1683 -msgid "Non-admins can create repository groups" -msgstr "" - -#: kallithea/model/db.py:1685 -msgid "Only admins can create user groups" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" msgstr "" #: kallithea/model/db.py:1686 -msgid "Non-admins can create user groups" -msgstr "" - -#: kallithea/model/db.py:1688 -msgid "Only admins can create top level repositories" +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" msgstr "" #: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 -msgid "Only admins can fork repositories" -msgstr "" - -#: kallithea/model/db.py:1695 -msgid "Non-admins can can fork repositories" -msgstr "" - #: kallithea/model/db.py:1698 -msgid "User registration with manual account activation" +msgid "Only admins can fork repositories" msgstr "" #: kallithea/model/db.py:1699 +msgid "Non-admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1702 +msgid "User registration with manual account activation" +msgstr "" + +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 msgid "Not reviewed" msgstr "" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 msgid "Under review" msgstr "" @@ -1827,7 +1836,7 @@ msgid "Enter %(min)i characters or more" msgstr "" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1920,7 +1929,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "" @@ -1947,15 +1956,15 @@ msgid "User \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups: %s" msgstr "" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "The password to your account %s has been changed using password reset form." msgstr "" @@ -1964,159 +1973,159 @@ msgid "Value cannot be an empty list" msgstr "" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with an alphanumeric character or underscore" msgstr "" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "" -#: kallithea/model/validators.py:153 -#, python-format -msgid "User group \"%(usergroup)s\" already exists" -msgstr "" - #: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character" msgstr "" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 msgid "Invalid repository URL" msgstr "" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "Invalid repository URL. It must be a valid http, https, ssh, svn+http or svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" msgstr "" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\"" msgstr "" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2243,7 +2252,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "" @@ -2291,7 +2300,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2301,7 +2310,7 @@ msgstr "" #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "" @@ -2316,7 +2325,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "" @@ -2324,7 +2333,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "" @@ -2336,7 +2345,7 @@ msgid "Forgot your password ?" msgstr "" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "" @@ -2453,10 +2462,6 @@ msgid "There are no branches yet" msgstr "" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2677,12 +2682,12 @@ msgid "Never" msgstr "" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "" @@ -2705,7 +2710,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2793,7 +2798,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2813,8 +2819,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2841,13 +2845,12 @@ msgstr "" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "" @@ -3028,7 +3031,7 @@ msgstr "" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "" @@ -3046,7 +3049,7 @@ msgstr "" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "" @@ -3228,7 +3231,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3476,6 +3479,11 @@ msgid "Unlock Repository" msgstr "" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "" @@ -3525,10 +3533,6 @@ msgid "Invalidate Repository Cache" msgstr "" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "Manually invalidate cache for this repository. On first access, the repository will be cached again." msgstr "" @@ -3966,7 +3970,7 @@ " {user} current user username,\n" " {netloc} network location/server host of running Kallithea server,\n" " {repo} full repository name,\n" -" {repoid} ID of repository, can be used to contruct clone-by-id" +" {repoid} ID of repository, can be used to construct clone-by-id" msgstr "" #: kallithea/templates/admin/settings/settings_visual.html:55 @@ -4210,21 +4214,17 @@ msgid "Files" msgstr "" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4234,111 +4234,116 @@ msgid "Compare" msgstr "" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4453,6 +4458,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "" @@ -4561,51 +4567,53 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, python-format +msgid "Changeset status: %s by %s" +msgstr "" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "" @@ -4621,7 +4629,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4651,21 +4659,21 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 msgid "Push new repository" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "" @@ -4683,13 +4691,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -4699,7 +4707,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -4726,8 +4734,8 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -4735,8 +4743,8 @@ msgstr[1] "" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4745,13 +4753,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 msgid "No revisions" msgstr "" @@ -4767,100 +4775,83 @@ msgid "on this changeset" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 msgid "Delete comment?" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 msgid "Status change" msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +msgid "Commenting on line." msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +msgid "Comments are in plain text. Use @username inside this text to notify another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +msgid "Finish pull request" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -msgid "Set changeset status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -msgid "Close" -msgstr "" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -4870,29 +4861,28 @@ msgid "Files affected" msgstr "" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "" @@ -4901,6 +4891,11 @@ msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -4913,29 +4908,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 #, python-format msgid "%s changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "" @@ -4946,28 +4941,28 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5026,15 +5021,19 @@ msgid "We have received a request to reset the password for your account." msgstr "" -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "This account is however managed outside this system and the password cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "Should you not be able to use the link above, please type the following code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "If it weren't you who requested the password reset, just disregard this message." msgstr "" @@ -5115,7 +5114,7 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" +msgid "New file type" msgstr "" #: kallithea/templates/files/files_add.html:64 @@ -5246,8 +5245,16 @@ msgid "Binary file (%s)" msgstr "" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "" + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." msgstr "" #: kallithea/templates/files/files_ypjax.html:5 @@ -5508,39 +5515,43 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "Pull requests do not change once created. Select a revision and save to replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 msgid "Remove reviewer" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -msgid "Potential Reviewers" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +msgid "Potential Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 -msgid "Cancel Changes" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 +msgid "Cancel Changes" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "" @@ -5551,7 +5562,7 @@ #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 #, python-format -msgid "Pull Requests from %s'" +msgid "Pull Requests from '%s'" msgstr "" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2015-05-28 22:41+0200\n" "Last-Translator: Sam Jaques \n" "Language-Team: Dutch (Belgium) " @@ -19,12 +19,12 @@ "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Weblate 2.3-dev\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Er zijn nog geen changesets" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -36,38 +36,30 @@ msgid "None" msgstr "Geen" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(gesloten)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "Toon witruimtes" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 #, fuzzy msgid "Ignore whitespace" msgstr "Negeer witruimtes" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, fuzzy, python-format msgid "Increase diff context to %(num)s lines" msgstr "vergroot de diff context met %(num)s lijnen" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "Deze revisie bestaat niet in deze repository" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" -"Het is niet toegestaan de status te wijzigen van een changeset " -"geassocieerd met een gesloten pull request" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "Selecteer de changeset" @@ -119,10 +111,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "" @@ -131,111 +123,111 @@ msgid "%s committed on %s" msgstr "" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "" -#: kallithea/controllers/files.py:537 -#, python-format -msgid "Unknown revision %s" -msgstr "" - #: kallithea/controllers/files.py:539 -msgid "Empty repository" +#, python-format +msgid "Unknown revision %s" msgstr "" #: kallithea/controllers/files.py:541 +msgid "Empty repository" +msgstr "" + +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "" @@ -249,7 +241,7 @@ msgid "Groups" msgstr "" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -257,23 +249,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "" @@ -284,158 +280,163 @@ msgstr "" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "" - -#: kallithea/controllers/login.py:202 +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" msgstr "" -#: kallithea/controllers/login.py:251 +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" msgstr "" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, fuzzy, python-format msgid "%s (closed)" msgstr "" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "" +#: kallithea/controllers/pullrequests.py:172 +msgid "Special" +msgstr "" + #: kallithea/controllers/pullrequests.py:173 -msgid "Special" -msgstr "" - -#: kallithea/controllers/pullrequests.py:174 msgid "Peer branches" msgstr "" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 -msgid "No permission to change pull request status" -msgstr "" - #: kallithea/controllers/pullrequests.py:727 +msgid "No permission to change pull request status" +msgstr "" + +#: kallithea/controllers/pullrequests.py:738 +#, python-format +msgid "Successfully deleted pull request %s" +msgstr "" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "" @@ -451,12 +452,12 @@ msgid "An error occurred during search operation." msgstr "" -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "" @@ -477,64 +478,64 @@ msgid "Error occurred during update of defaults" msgstr "" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "" + +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 #: kallithea/controllers/admin/users.py:285 -msgid "Forever" +msgid "5 minutes" msgstr "" #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" +msgid "1 hour" msgstr "" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" +msgid "1 day" msgstr "" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "" @@ -549,7 +550,7 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "" @@ -559,33 +560,33 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -635,10 +636,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "" @@ -669,7 +670,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "" @@ -681,7 +682,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "" @@ -702,244 +703,244 @@ msgid "Error occurred during update of permissions" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "" @@ -980,76 +981,80 @@ msgstr "" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "" @@ -1065,125 +1070,125 @@ msgid "No changes detected" msgstr "" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "" -#: kallithea/lib/helpers.py:696 -msgid "and" -msgstr "" - #: kallithea/lib/helpers.py:697 +msgid "and" +msgstr "" + +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "" @@ -1193,8 +1198,8 @@ msgstr "" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "" @@ -1218,7 +1223,7 @@ msgid "chmod" msgstr "" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1226,69 +1231,69 @@ "repositories" msgstr "" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "" msgstr[1] "" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "" @@ -1387,7 +1392,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "" @@ -1498,7 +1503,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "" @@ -1513,7 +1518,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "" @@ -1540,7 +1545,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "" @@ -1687,7 +1692,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "" @@ -1714,12 +1719,12 @@ msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1728,104 +1733,104 @@ msgid "on line %s" msgstr "" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 -msgid "Default user has read access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1669 -msgid "Default user has write access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" +msgid "Default user has read access to new repositories" msgstr "" #: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" +msgid "Default user has write access to new repositories" msgstr "" #: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" msgstr "" #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -msgid "Only admins can create repository groups" +msgid "Default user has read access to new user groups" msgstr "" #: kallithea/model/db.py:1683 -msgid "Non-admins can create repository groups" -msgstr "" - -#: kallithea/model/db.py:1685 -msgid "Only admins can create user groups" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" msgstr "" #: kallithea/model/db.py:1686 -msgid "Non-admins can create user groups" -msgstr "" - -#: kallithea/model/db.py:1688 -msgid "Only admins can create top level repositories" +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" msgstr "" #: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 -msgid "Only admins can fork repositories" -msgstr "" - -#: kallithea/model/db.py:1695 -msgid "Non-admins can can fork repositories" -msgstr "" - #: kallithea/model/db.py:1698 -msgid "User registration with manual account activation" +msgid "Only admins can fork repositories" msgstr "" #: kallithea/model/db.py:1699 +msgid "Non-admins can fork repositories" +msgstr "" + +#: kallithea/model/db.py:1702 +msgid "User registration with manual account activation" +msgstr "" + +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "" @@ -1848,7 +1853,7 @@ msgid "Enter %(min)i characters or more" msgstr "" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1941,7 +1946,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "" @@ -1974,15 +1979,15 @@ "owners or remove those user groups: %s" msgstr "" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -1993,167 +1998,167 @@ msgid "Value cannot be an empty list" msgstr "" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "" -#: kallithea/model/validators.py:153 -#, python-format -msgid "User group \"%(usergroup)s\" already exists" -msgstr "" - #: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" msgstr "" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 msgid "Invalid repository URL" msgstr "" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" msgstr "" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2280,7 +2285,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "" @@ -2328,7 +2333,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2338,7 +2343,7 @@ msgstr "" #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "" @@ -2353,7 +2358,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "" @@ -2361,7 +2366,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "" @@ -2373,7 +2378,7 @@ msgid "Forgot your password ?" msgstr "" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "" @@ -2494,10 +2499,6 @@ msgid "There are no branches yet" msgstr "" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2724,12 +2725,12 @@ msgid "Never" msgstr "" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "" @@ -2752,7 +2753,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2840,7 +2841,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2860,8 +2862,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2888,13 +2888,12 @@ msgstr "" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "" @@ -3075,7 +3074,7 @@ msgstr "" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "" @@ -3093,7 +3092,7 @@ msgstr "" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "" @@ -3291,7 +3290,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3554,6 +3553,11 @@ msgid "Unlock Repository" msgstr "" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "" @@ -3611,10 +3615,6 @@ msgid "Invalidate Repository Cache" msgstr "" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -4345,21 +4345,17 @@ msgid "Files" msgstr "" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4369,111 +4365,116 @@ msgid "Compare" msgstr "" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4575,7 +4576,6 @@ #: kallithea/templates/base/root.html:31 #, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "Statuswijziging -> %s" @@ -4585,7 +4585,6 @@ #: kallithea/templates/base/root.html:33 #, fuzzy -#| msgid "Show Selected Changeset __S" msgid "Show Selected Changesets {0} → {1}" msgstr "Selecteer de changeset" @@ -4595,6 +4594,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "" @@ -4704,51 +4704,54 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Set changeset status" +msgid "Changeset status: %s by %s" +msgstr "Selecteer de changeset" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "" @@ -4764,7 +4767,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4794,21 +4797,21 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 msgid "Push new repository" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "" @@ -4826,13 +4829,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -4842,7 +4845,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -4869,8 +4872,8 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -4878,8 +4881,8 @@ msgstr[1] "" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4888,13 +4891,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 msgid "No revisions" msgstr "" @@ -4912,104 +4915,90 @@ msgid "on this changeset" msgstr "Selecteer de changeset" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 #, fuzzy msgid "Delete comment?" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "Statuswijziging -> %s" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +msgid "Commenting on line." msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +#, fuzzy +msgid "Set changeset status" +msgstr "Selecteer de changeset" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Statuswijziging -> %s" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +#, fuzzy +msgid "Close" +msgstr "(gesloten)" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, fuzzy, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -#, fuzzy -msgid "Set changeset status" -msgstr "Selecteer de changeset" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -#, fuzzy -msgid "Close" -msgstr "(gesloten)" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5019,29 +5008,28 @@ msgid "Files affected" msgstr "" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "" @@ -5050,6 +5038,11 @@ msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5062,29 +5055,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 #, fuzzy, python-format msgid "%s changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "" @@ -5095,28 +5088,28 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5175,17 +5168,23 @@ msgid "We have received a request to reset the password for your account." msgstr "" -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5268,7 +5267,7 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" +msgid "New file type" msgstr "" #: kallithea/templates/files/files_add.html:64 @@ -5400,8 +5399,16 @@ msgid "Binary file (%s)" msgstr "" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "" + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." msgstr "" #: kallithea/templates/files/files_ypjax.html:5 @@ -5664,42 +5671,48 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 #, fuzzy msgid "Remove reviewer" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -#, fuzzy -msgid "Potential Reviewers" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +#, fuzzy +msgid "Potential Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 #, fuzzy msgid "Cancel Changes" msgstr "Selecteer de changeset" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "" @@ -5709,9 +5722,9 @@ msgstr "" #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 -#, python-format -msgid "Pull Requests from %s'" -msgstr "" +#, fuzzy, python-format +msgid "Pull Requests from '%s'" +msgstr "Statuswijziging -> %s" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 #, python-format @@ -6281,39 +6294,12 @@ #~ msgid "Your password reset link was sent" #~ msgstr "" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "" #~ msgid "Your new Kallithea password:%s" #~ msgstr "" -#~ msgid "" -#~ "Password reset link will be sent " -#~ "to the email address matching your " -#~ "username." -#~ msgstr "" - #~ msgid "Open New Pull Request for Selected Changesets" #~ msgstr "" @@ -6332,3 +6318,53 @@ #~ msgid "Created by" #~ msgstr "" +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "You can only edit files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Non-admins can can fork repositories" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "" + +#~ msgid "Commenting on line {1}." +#~ msgstr "" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "" + +#~ msgid "Preview" +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user." +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "File is too big to display" +#~ msgstr "" + +#~ msgid "Save as New Pull Request" +#~ msgstr "" + +#~ msgid "Pull Requests from %s'" +#~ msgstr "" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/pl/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/pl/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/pl/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2015-04-04 09:08+0200\n" "Last-Translator: Andrew Shadura \n" "Language-Team: Polish " @@ -24,12 +24,12 @@ "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 2.3-dev\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Brak zestawienia zmian" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -41,37 +41,29 @@ msgid "None" msgstr "Brak" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(zamknięty)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "pokazuj spacje" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "Ignoruj pokazywanie spacji" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" -"Zmiana statusu na grupy zmian powiązania łączy zamkniętego wniosku jest " -"niedozwolona" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "Wybrane zmiany" @@ -128,10 +120,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "Lista zmian była zbyt duża i została ucięta..." @@ -140,111 +132,113 @@ msgid "%s committed on %s" msgstr "%s zakomitowal w %s" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "Kliknij tutaj, by dodać nowy plik" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, fuzzy, python-format msgid "%s at %s" msgstr "w %s i %s" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "Repozytorium zostało zablokowane przez %s na %s" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +#, fuzzy +msgid "You can only delete files with revision being a valid branch" +msgstr "Można tylko edytować pliki z rewizji obecnej gałęzi " + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "Wystąpił błąd w trakcie zatwierdzania" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " +#: kallithea/controllers/files.py:379 +#, fuzzy +msgid "You can only edit files with revision being a valid branch" msgstr "Można tylko edytować pliki z rewizji obecnej gałęzi " -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "Edytowanie %s w Kallithea" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Bez zmian" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "Committ wykonany do %s" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Dodano %s poprzez Kallithea" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Brak treści" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "Brak nazwy pliku" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "Lokalizacja musi być ścieżką względną i nie może zawierać .. ścieżki" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "Pobieranie wyłączone" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Nieznana wersja %s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Puste repozytorium" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "Nieznany typ archiwum" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Różnice" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "Gałęzie" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Etykiety" @@ -258,7 +252,7 @@ msgid "Groups" msgstr "" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -266,23 +260,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Repozytoria" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "gałąź" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "Zamknięte Gałęzie" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "Tag" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "Bookmark" @@ -293,164 +291,167 @@ msgstr "Dziennik Publiczny" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "Dziennik" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "Udało Ci się zarejestrować na stronie" - -#: kallithea/controllers/login.py:202 -#, fuzzy -#| msgid "Your password reset link was sent" +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "Udało Ci się zarejestrować w %s" + +#: kallithea/controllers/login.py:195 +#, fuzzy msgid "A password reset confirmation code has been sent" msgstr "Twój link zresetowania hasła został wysłany" -#: kallithea/controllers/login.py:251 -#, fuzzy -#| msgid "Password reset link" +#: kallithea/controllers/login.py:244 +#, fuzzy msgid "Invalid password reset token" msgstr "łącze resetowania hasła" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (zamknięty)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "Grupy zmian" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "Specjalne" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "gałęzie" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Zakładki" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "Brak opisu" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "Prośba o wykonanie połączenia gałęzi została wykonana prawidłowo" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "Wystąpił błąd podczas prośby o połączenie gałęzi" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "Recenzje wniosków połączenia gałęzi" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 #, fuzzy msgid "Pull request updated" msgstr "Połączone gałęzie" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 +#: kallithea/controllers/pullrequests.py:727 #, fuzzy msgid "No permission to change pull request status" msgstr "Zagłosuj na żądanie na grupę zmian" -#: kallithea/controllers/pullrequests.py:727 +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "Zamknięcie." @@ -466,13 +467,13 @@ msgid "An error occurred during search operation." msgstr "Wystąpił błąd podczas operacji wyszukiwania." -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 #, fuzzy msgid "No data ready yet" msgstr "Żadne dane nie zostały załadowane" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "Statystyki są wyłączone dla tego repozytorium" @@ -493,66 +494,66 @@ msgid "Error occurred during update of defaults" msgstr "wystąpił błąd podczas aktualizacji wartości domyślnych" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 -#: kallithea/controllers/admin/users.py:285 +#: kallithea/controllers/admin/users.py:284 #, fuzzy msgid "Forever" msgstr "na zawsze" -#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/gists.py:59 #: kallithea/controllers/admin/my_account.py:244 -#: kallithea/controllers/admin/users.py:286 +#: kallithea/controllers/admin/users.py:285 msgid "5 minutes" msgstr "5 minut" +#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/users.py:286 +msgid "1 hour" +msgstr "1 godzina" + #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "1 godzina" - -#: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 -#: kallithea/controllers/admin/users.py:288 msgid "1 day" msgstr "1 dzień" -#: kallithea/controllers/admin/gists.py:63 +#: kallithea/controllers/admin/gists.py:62 #: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 +#: kallithea/controllers/admin/users.py:288 msgid "1 month" msgstr "1 miesiąc" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "Czas życia" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Wystąpił błąd podczas tworzenia git" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "Usuń gist %s" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 #, fuzzy msgid "Unmodified" msgstr "Ostatnio modyfikowany" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "" @@ -569,7 +570,7 @@ msgstr "Twoje konto zostało pomyślnie zaktualizowane" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "wystąpił błąd podczas aktualizacji użytkownika %s" @@ -579,33 +580,33 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "Dodano e-mail %s do użytkownika" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "Wystąpił błąd podczas zapisywania e-maila" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "Usunięto e-mail użytkownikowi" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -655,10 +656,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "Administracja" @@ -689,7 +690,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "Ręczna aktywacja nowego konta" @@ -701,7 +702,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "Automatyczna aktywacja nowego konta" @@ -722,246 +723,246 @@ msgid "Error occurred during update of permissions" msgstr "Wystąpił błąd podczas aktualizacji uprawnień" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "Wystąpił błąd podczas tworzenia grupy repo %s" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "Utworzono grupę repo %s" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "Zaktualizowano grupę repo %s" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "Wystąpił błąd podczas aktualizacji grupy repo %s" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "Ta grupa zawiera %s repozytorium i nie może być usunięta" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "Ta grupa zawiera %s repozytorium i nie może być usunięta" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "Usunięto grupę repo %s" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "Wystąpił błąd podczas usuwania z repozytorium grupy %s" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "Nie można cofnąć zezwolenia dla admina jako admin" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "Aktualizacja uprawnień grup repozytorium" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "Wystąpił błąd podczas cofania zezwolenia" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "utworzone repozytorium %s" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "utworzone repozytorium %s z %s" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "Gałęzi %s w repozytorium %s" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "Utworzone repozytorium %s" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "Repozytorium %s zostało pomyślnie zaktualizowane" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "Wystąpił błąd podczas aktualizacji repozytorium %s" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "Oderwane rozgałęzienie %s" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "Usunięte repozytorium %s" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "Usunięte repozytorium %s" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, fuzzy, python-format msgid "Cannot delete repository %s which still has forks" msgstr "Nie można usunąć %s nadal zawiera załączniki rozgałęzienia" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "Wystąpił błąd podczas usuwania %s" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "Uprawnienia repozytorium zostały zaktualizowane" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "Wystąpił błąd podczas tworzenia użytkownika %s" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "Wystąpił błąd podczas zapisywania e-maila" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "-- Brak rozgalezienia --" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "Zaktualizowano widoczność stron w publicznym dzienniku" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "Wystąpił błąd podczas ustawiania tego repozytorium w dzienniku publicznym" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "Brak" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "Oznaczono %s repo jako rozwidlenie %s" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "Wystąpił błąd podczas tej operacji" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "Repozytorium nie jest zablokowane" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "Repozytorium nie jest zablokowane" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "Wystąpił błąd podczas odblokowywania" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "Cache wyczyszczony poprawnie" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "Wystąpił błąd podczas unieważniania cache" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "Pobieranie z lokalizacji zdalnej" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "Wystąpił błąd podczas pobierania z lokalizacji zdalnej" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "Wystąpił błąd podczas usuwania z repozytorium statystyk" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "Aktualizacja ustawień VCS" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "Wystąpił błąd podczas aktualizacji ustawień aplikacji" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" "Repozytoria z powodzeniem zostały ponownie zeskanowane dodano: %s, " "usunięto: %s." -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "Aktualizacja ustawień aplikacji" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "Aktualizacja ustawień wizualizacji" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "Wystąpił błąd podczas aktualizacji ustawień wizualizacji" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "Proszę podać adres email" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "Dodano nowy hook" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "Aktualizacja hooku" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "Wystąpił błąd podczas tworzenia hooku" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "Zadanie ponownej indeksacji whoosh zostało zaplanowane" @@ -1002,76 +1003,80 @@ msgstr "Aktualizacja uprawnień grupy użytkowników" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "Aktualizacja uprawnień" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "Wystąpił błąd podczas zapisywania uprawnień" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "Utworzono użytkownika %s" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "Wystąpił błąd podczas tworzenia użytkownika %s" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "Użytkownik został zaktualizowany" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "Użytkownik został usunięty" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "Wystąpił błąd podczas usuwania użytkownika" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "Dodano ip %s do listy dozwolonych adresów użytkownia" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "Wystąpił błąd podczas zapisywania e-maila" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "Usunięto adres ip z listy dozwolonych adresów dla użytkownika" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "Obserwatorzy %s" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "Musisz być zarejestrowanym użytkownikiem, żeby wykonać to działanie" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "Musisz być zalogowany, żeby oglądać stronę" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "Nie znaleziono changeset" @@ -1089,126 +1094,126 @@ msgid "No changes detected" msgstr "Nie wykryto zmian" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "Usunięta gałąź: %s" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "Utworzony tag: %s" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "Pokaż wszystkie zestawienia zmian changesets %s->%s" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 #, fuzzy msgid "Compare view" msgstr "Wyświetl porównanie" -#: kallithea/lib/helpers.py:696 +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "i" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "%s więcej" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "rewizja" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, fuzzy, python-format msgid "Fork name %s" msgstr "nazwa rozgałęzienia %s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, fuzzy, python-format msgid "Pull request %s" msgstr "Połączonych gałęzi #%s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "[usunięte] repozytorium" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "[utworzone] repozytorium" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "[utworzone] repozytorium jako rozgałęzienie" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "[rozgałęzione] repozytorium" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "[zaktualizowane] repozytorium" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "[pobierz] archiwum z repozytorium" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "[skasowane] repozytorium" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "[utworzony] użytkownik" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "[zaktualizowany] użytkownik" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "[utworzona] grupa użytkowników" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "[zaktualizowana] grupa użytkowników" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "[komentarz] do zmiany w repozytorium" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "[komentarz] wniosek o połączenie gałęzi" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "[zamknięty] wniosek o połączenie gałęzi" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[wysłane zmiany] w" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "[synchronizacja przez Kallithea] z repozytorium" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "[pobieranie z zdalnego] do repozytorium" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "[pobrano]" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "[start następnego] repozytorium" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "[zatrzymany po] repozytorium" @@ -1218,8 +1223,8 @@ msgstr " i %s więcej" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "Brak plików" @@ -1243,7 +1248,7 @@ msgid "chmod" msgstr "chmod" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1254,7 +1259,7 @@ "zmienione z systemie plików proszę uruchomić aplikację ponownie, aby " "ponownie przeskanować repozytoria" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" @@ -1262,7 +1267,7 @@ msgstr[1] "%d lata" msgstr[2] "%d lat" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" @@ -1270,7 +1275,7 @@ msgstr[1] "%d miesięcy" msgstr[2] "%d miesięcy" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" @@ -1278,7 +1283,7 @@ msgstr[1] "%d dni" msgstr[2] "%d dni" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" @@ -1286,7 +1291,7 @@ msgstr[1] "%d godziny" msgstr[2] "%d godzin" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" @@ -1294,7 +1299,7 @@ msgstr[1] "%d minuty" msgstr[2] "%d minut" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" @@ -1302,27 +1307,27 @@ msgstr[1] "%d sekund" msgstr[2] "%d sekund" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "w %s" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "%s temu" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "w %s i %s" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "%s i %s temu" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "przed chwilą" @@ -1421,7 +1426,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Administrator Repo" @@ -1532,7 +1537,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "Zaakceptowano" @@ -1547,7 +1552,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "Odrzucono" @@ -1574,7 +1579,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "najwyższy poziom" @@ -1721,7 +1726,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "Rejestracja wyłączona" @@ -1748,12 +1753,12 @@ msgstr "Rejestracja użytkownika z automatyczną aktywacją konta" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1762,114 +1767,114 @@ msgid "on line %s" msgstr "widziany %s" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[Wymieniony]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 +#: kallithea/model/db.py:1672 #, fuzzy msgid "Default user has read access to new repositories" msgstr "Nieautoryzowany dostęp do zasobów" -#: kallithea/model/db.py:1669 +#: kallithea/model/db.py:1673 #, fuzzy msgid "Default user has write access to new repositories" msgstr "Nieautoryzowany dostęp do zasobów" -#: kallithea/model/db.py:1670 +#: kallithea/model/db.py:1674 msgid "Default user has admin access to new repositories" msgstr "" -#: kallithea/model/db.py:1672 +#: kallithea/model/db.py:1676 msgid "Default user has no access to new repository groups" msgstr "" -#: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" -msgstr "" - #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 +msgid "Default user has read access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1683 +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1686 #, fuzzy msgid "Only admins can create repository groups" msgstr "Utworzono grupę repo %s" -#: kallithea/model/db.py:1683 +#: kallithea/model/db.py:1687 #, fuzzy msgid "Non-admins can create repository groups" msgstr "Utworzono grupę repo %s" -#: kallithea/model/db.py:1685 +#: kallithea/model/db.py:1689 #, fuzzy msgid "Only admins can create user groups" msgstr "Tworzenie grup użytkowników" -#: kallithea/model/db.py:1686 +#: kallithea/model/db.py:1690 #, fuzzy msgid "Non-admins can create user groups" msgstr "Tworzenie grup użytkowników" -#: kallithea/model/db.py:1688 +#: kallithea/model/db.py:1692 msgid "Only admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1689 +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 #, fuzzy msgid "Only admins can fork repositories" msgstr "Ogólna liczba repozytoriów" -#: kallithea/model/db.py:1695 -#, fuzzy -msgid "Non-admins can can fork repositories" +#: kallithea/model/db.py:1699 +#, fuzzy +msgid "Non-admins can fork repositories" msgstr "Unieważnia cache dla wszystkich repozytoriów" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 #, fuzzy msgid "User registration with manual account activation" msgstr "Rejestracja użytkownika z ręczną aktywacją konta" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 #, fuzzy msgid "User registration with automatic account activation" msgstr "Rejestracja użytkownika z automatyczną aktywacją konta" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "Brak Korekty" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "Objęty Przeglądem" @@ -1892,7 +1897,7 @@ msgid "Enter %(min)i characters or more" msgstr "Wpisz %(min)i lub więcej znaków" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1968,14 +1973,11 @@ #: kallithea/model/notification.py:307 #, fuzzy, python-format -#| msgid "%(user)s wants you to review pull request %(pr_nice_id)s: -#| %(pr_title)s" msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "%(user)s chce żeby przejrzeć nowe gałęzie #%(pr_id)s: %(pr_title)s" #: kallithea/model/notification.py:308 #, fuzzy, python-format -#| msgid "[commented] on pull request for" msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "[komentarz] wniosek o połączenie gałęzi" @@ -1989,7 +1991,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "%(user)s chce żeby przejrzeć nowe gałęzie #%(pr_id)s: %(pr_title)s" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "ostatni tip" @@ -2031,17 +2033,16 @@ "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może " "zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "łącze resetowania hasła" -#: kallithea/model/user.py:408 -#, fuzzy -#| msgid "Password reset link" +#: kallithea/model/user.py:418 +#, fuzzy msgid "Password reset notification" msgstr "łącze resetowania hasła" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -2052,17 +2053,17 @@ msgid "Value cannot be an empty list" msgstr "Wartość listy nie może być pusta" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "Użytkownik \"%(username)s\" już istnieje" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, fuzzy, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "Nazwa użytkownika %(username)s jest nieprawidłowa" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 #, fuzzy msgid "" "Username may only contain alphanumeric characters underscores, periods or" @@ -2072,25 +2073,25 @@ " kropki lub myślniki i muszą zaczynać się znakiem alfanumerycznym lub " "podkreśleniem" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "Nazwa użytkownika %(username)s jest nieprawidłowa" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "Niewłaściwa nazwa grupy" -#: kallithea/model/validators.py:153 +#: kallithea/model/validators.py:155 #, python-format msgid "User group \"%(usergroup)s\" already exists" msgstr "Nazwa grupy \"%(usergroup)s\" już istnieje" -#: kallithea/model/validators.py:155 +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" @@ -2098,107 +2099,107 @@ "nazwa grupy może zawierać tylko znaki alfanumeryczne, podkreślenia, " "kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "Nie można przypisać do tej grupy jako rodzic" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "Nazwa grupy \"%(group_name)s\" już istnieje" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "Repozytorium o nazwie \"%(group_name)s\" już istnieje" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "Nieprawidłowe znaki (nie-ascii) w haśle" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "Hasła różnią się" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 #, fuzzy msgid "Invalid username or password" msgstr "nieprawidłowe hasło" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "Niezgodność tokenu" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, fuzzy, python-format msgid "Repository name %(repo)s is not allowed" msgstr "Nazwa repozytorium %(repo)s jest zabroniona" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "Repozytorium o nazwie %(repo)s już istnieje" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "Repozytorium \"%(repo)s\" już istnieje w grupie \"%(group)s\"" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "Grupa repozytoriów z nazwą \"%(repo)s\" już istnieje" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy msgid "Invalid repository URL" msgstr "prywatne repozytorium" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "Fork musi być tego samego typu, jak rodzic" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "Nie masz uprawnień do tworzenia repozytorium w tej grupie" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "nie masz uprawnień do tworzenia repozytorium w tej grupie" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "Nie masz uprawnień do tworzenia repozytorium w tej grupie" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "Ta nazwa użytkownika lub grupy użytkowników nie jest prawidłowa" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "To nie jest prawidłowa ścieżka" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 #, fuzzy msgid "This email address is already in use" msgstr "Ten adres e-mail jest już zajęty" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, fuzzy, python-format msgid "Email address \"%(email)s\" not found" msgstr "e-mail \"%(email)s\" nie istnieje." -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" @@ -2206,26 +2207,26 @@ "Atrybut logowania CN do LDAP należy określić, jest to nazwa atrybutu, " "który jest odpowiednikiem \"username\"" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "Proszę podać poprawny adres IPv4 lub IPv6" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "Rozmiar sieci (bits) może mieścić się w zakresie od 0-32 (nie %(bits)r)" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" "Klucz nazwy może składać się tylko z liter, podkreślenia, myślnika lub " "numerów" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "Nazwa pliku nie może znajdować się w katalogu" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2352,7 +2353,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "Właściciel" @@ -2400,7 +2401,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2410,7 +2411,7 @@ msgstr "Wczytywanie..." #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "Zaloguj się" @@ -2425,7 +2426,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "Nazwa użytkownika" @@ -2433,7 +2434,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "Hasło" @@ -2445,7 +2446,7 @@ msgid "Forgot your password ?" msgstr "Zapomniałeś hasła?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "Nie masz konta?" @@ -2486,8 +2487,6 @@ #: kallithea/templates/password_reset.html:47 #, fuzzy -#| msgid "" "Password reset link will be sent to the email address matching -#| your " "username." msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." @@ -2510,13 +2509,11 @@ #: kallithea/templates/password_reset_confirmation.html:39 #, fuzzy -#| msgid "New password" msgid "New Password" msgstr "Nowe hasło" #: kallithea/templates/password_reset_confirmation.html:48 #, fuzzy -#| msgid "Your new password" msgid "Confirm New Password" msgstr "Nowe hasło" @@ -2575,10 +2572,6 @@ msgid "There are no branches yet" msgstr "Nie ma jeszcze gałęzi" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "Zamknięte Gałęzie" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2812,12 +2805,12 @@ msgid "Never" msgstr "nigdy" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "Zaktualizuj Gist" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "Anuluj" @@ -2840,7 +2833,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "Utwórz Nowy Gist" @@ -2928,7 +2921,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2948,8 +2942,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2976,13 +2968,12 @@ msgstr "utworzono" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "wyświetl jako raw" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "Moje konto" @@ -3175,7 +3166,7 @@ msgstr "Komentarze" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "Połączone gałęzie" @@ -3195,7 +3186,7 @@ msgstr "Pokaż powiadomienia" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "Powiadomienia" @@ -3410,7 +3401,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3695,6 +3686,11 @@ msgid "Unlock Repository" msgstr "Odblokowane repozytorium" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 #, fuzzy msgid "Confirm to lock repository." @@ -3756,11 +3752,6 @@ msgid "Invalidate Repository Cache" msgstr "Unieważnij pamięć podręczną repozytorium" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -#, fuzzy -msgid "Confirm to invalidate repository cache." -msgstr "Potwierdź unieważnienie pamięci podręcznej repozytorium" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 #, fuzzy msgid "" @@ -4522,22 +4513,18 @@ msgid "Files" msgstr "Pliki" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "Przełącz do" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "Opcje" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 #, fuzzy msgid "Compare Fork" msgstr "Porównaj rozwidlenie" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4547,117 +4534,122 @@ msgid "Compare" msgstr "Porównaj" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "Szukaj" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "Odblokowany" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "zablokowane" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "Obserwuj" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "Nie obserwuj" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "Nie obserwuj" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "Gałąź" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "Stwórz nowe żądanie połączenia gałęzi" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "Pokaż Prośby Pobrania %s" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "Przełącz do" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "Pokaż ostatnią aktywność" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "Dziennik publiczny" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "Wyświetl publiczne gists" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "Gists" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 #, fuzzy msgid "All Public Gists" msgstr "Wszystkie publiczne gists" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 #, fuzzy msgid "My Public Gists" msgstr "Moje publiczne gists" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 #, fuzzy msgid "My Private Gists" msgstr "Moje prywatne gists" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "Szukaj we wszystkich repozytoriach" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 #, fuzzy msgid "My Pull Requests" msgstr "Połączone gałęzie" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 #, fuzzy msgid "Not Logged In" msgstr "Zaloguj się" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 #, fuzzy msgid "Login to Your Account" msgstr "Zaloguj się do swojego konta" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "Nie pamiętasz hasła?" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "Wyloguj się" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4766,7 +4758,6 @@ #: kallithea/templates/base/root.html:31 #, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "Komentarz połączenia gałęzi %s" @@ -4776,7 +4767,6 @@ #: kallithea/templates/base/root.html:33 #, fuzzy -#| msgid "Show Selected Changesets __S → __E" msgid "Show Selected Changesets {0} → {1}" msgstr "Pokaż wybrane zmiany __S -> __E" @@ -4787,6 +4777,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 #, fuzzy msgid "Collapse Diff" msgstr "Pliki różnic" @@ -4901,52 +4892,56 @@ #: kallithea/templates/changelog/changelog.html:92 #: kallithea/templates/changelog/changelog_summary_data.html:20 #, fuzzy, python-format +#| msgid "" "Changeset status: %s\n" "Click to open associated pull request %s" msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "Status grupy zmian: %s⏎ Kliknij, aby otworzyć prośby pobrania #%s" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Changeset status: %s" +msgid "Changeset status: %s by %s" msgstr "Status grupy zmian: %s" -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "Komentarze Grupy zmian" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "Zakładki %s" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "Tagi %s" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "Gałęzie %s" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "Nie ma jeszcze zmian" @@ -4962,7 +4957,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "Dodana" @@ -4992,22 +4987,22 @@ msgid "Refs" msgstr "Gałąź/Etykieta" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "Dodaj lub prześlij pliki bezpośrednio przez stronę" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy msgid "Push new repository" msgstr "Wyślij zmiany do nowego repo" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "Istniejące repozytorium?" @@ -5025,13 +5020,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "Status grupy zmian" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "Raw różnic" @@ -5041,7 +5036,7 @@ msgstr "Poprawka różnic" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "Pobierz różnice" @@ -5072,8 +5067,8 @@ msgstr "utworzono" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -5082,8 +5077,8 @@ msgstr[2] "%s plików zostało zmienionych" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -5093,14 +5088,14 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 #, fuzzy msgid "Show full diff anyway" msgstr "Pokaż pełną historię" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy msgid "No revisions" msgstr "rewizja" @@ -5120,66 +5115,76 @@ msgid "on this changeset" msgstr "Brak zestawienia zmian" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 #, fuzzy msgid "Delete comment?" msgstr "%d komentarz" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "Ostatnia aktywność" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +#, fuzzy +msgid "Commenting on line." msgstr "Komentując linię {1}." #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "Komentarze analizowane za pomocą %s składni od %s wsparcia." - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -#, fuzzy -msgid "Use @username inside this text to notify another user" +#, fuzzy +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." msgstr "" "Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do " "użytkownika strony" -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "Podgląd komentarza" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +#: kallithea/templates/changeset/changeset_file_comment.html:67 +#, fuzzy +msgid "Set changeset status" +msgstr "Zmiana statusu grupy zmian" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "Zagłosuj na żądanie na grupę zmian" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +#, fuzzy +msgid "No change" +msgstr "Bez zmian" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Komentarz połączenia gałęzi %s" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +#, fuzzy +msgid "Close" +msgstr "(zamknięty)" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 #, fuzzy msgid "Submitting ..." msgstr "Przesyłanie..." -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "Komentarz" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "Podgląd" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "Musisz być zalogowany żeby komentarz." -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "Zaloguj się teraz" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "Ukryj" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" @@ -5187,7 +5192,7 @@ msgstr[1] "%d komentarzy" msgstr[2] "%d komentarzy" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, fuzzy, python-format msgid "%d inline" msgid_plural "%d inline" @@ -5195,7 +5200,7 @@ msgstr[1] "(%d linii)" msgstr[2] "(%d linii)" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, fuzzy, python-format msgid "%d general" msgid_plural "%d general" @@ -5203,32 +5208,6 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -#, fuzzy -msgid "Use @username inside this text to notify another user." -msgstr "" -"Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do " -"użytkownika strony" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "Zagłosuj na żądanie na grupę zmian" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -#, fuzzy -msgid "Set changeset status" -msgstr "Zmiana statusu grupy zmian" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -#, fuzzy -msgid "No change" -msgstr "Bez zmian" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -#, fuzzy -msgid "Close" -msgstr "(zamknięty)" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5238,31 +5217,30 @@ msgid "Files affected" msgstr "Pliki naruszone" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +#, fuzzy +msgid "Deleted" +msgstr "usuń" + +#: kallithea/templates/changeset/diff_block.html:57 +#, fuzzy +msgid "Renamed" +msgstr "zmień nazwę" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "Pokaż pełną edycję tego pliku" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "Pokaż pełną listę zmian i różnic obok siebie" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "Pokaż online komentarz" -#: kallithea/templates/changeset/diff_block.html:86 -#, fuzzy -msgid "Deleted" -msgstr "usuń" - -#: kallithea/templates/changeset/diff_block.html:89 -#, fuzzy -msgid "Renamed" -msgstr "zmień nazwę" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "Brak zestawienia zmian" @@ -5271,6 +5249,11 @@ msgid "Ancestor" msgstr "Przodek" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "Status grupy zmian: %s" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5283,32 +5266,32 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 #, fuzzy msgid "Show merge diff" msgstr "Pokaż pełną historię" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 #, fuzzy msgid "Common ancestor" msgstr "Skomentuj grupę zmian" -#: kallithea/templates/compare/compare_cs.html:90 +#: kallithea/templates/compare/compare_cs.html:99 msgid "No common ancestor found - repositories are unrelated" msgstr "" -#: kallithea/templates/compare/compare_cs.html:98 +#: kallithea/templates/compare/compare_cs.html:107 #, fuzzy msgid "is" msgstr "Gist" -#: kallithea/templates/compare/compare_cs.html:99 +#: kallithea/templates/compare/compare_cs.html:108 #, fuzzy, python-format msgid "%s changesets" msgstr "%s Zestawienie zmian" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 #, fuzzy msgid "behind" msgstr "Indeksuj ponownie" @@ -5320,20 +5303,20 @@ msgstr "%s Porównaj" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" @@ -5341,8 +5324,8 @@ msgstr[1] "Pokaż %s komentarze" msgstr[2] "Pokaż %s komentarze" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "Pokaż pełną historię" @@ -5399,21 +5382,26 @@ #: kallithea/templates/email_templates/password_reset.html:6 #, fuzzy -#| msgid "We received a request to create a new password for your account." msgid "We have received a request to reset the password for your account." msgstr "Otrzymaliśmy prośbę o utworzenie nowego hasła do twojego konta." -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5498,8 +5486,9 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "Nowy tryb pliku" +#, fuzzy +msgid "New file type" +msgstr "nowy plik" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5635,10 +5624,21 @@ msgid "Binary file (%s)" msgstr "Plik binarny (%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +#, fuzzy +msgid "File is too big to display." msgstr "Plik jest za duży do wyświetlenia" +#: kallithea/templates/files/files_source.html:76 +#, fuzzy +msgid "Show full annotation anyway." +msgstr "Pokaż pełną historię" + +#: kallithea/templates/files/files_source.html:78 +#, fuzzy +msgid "Show as raw." +msgstr "wyświetl jako raw" + #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" msgstr "adnotacja" @@ -5909,44 +5909,50 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 #, fuzzy msgid "Pull Request Reviewers" msgstr "Recenzje wniosków połączenia gałęzi" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 #, fuzzy msgid "Remove reviewer" msgstr "recenzent" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 +#: kallithea/templates/pullrequests/pullrequest_show.html:261 msgid "Type name of reviewer to add" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:258 +#: kallithea/templates/pullrequests/pullrequest_show.html:269 #, fuzzy msgid "Potential Reviewers" msgstr "Podgląd komentarza" -#: kallithea/templates/pullrequests/pullrequest_show.html:261 +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -#, fuzzy -msgid "Save as New Pull Request" +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#, fuzzy +msgid "Save Updates as New Pull Request" msgstr "Otwórz nową prośbę o połączenie gałęzi" -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:297 #, fuzzy msgid "Cancel Changes" msgstr "Ostatnia aktywność" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 #, fuzzy msgid "Pull Request Content" msgstr "Wniosek połączenia zmienił status" @@ -5958,7 +5964,7 @@ #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 #, fuzzy, python-format -msgid "Pull Requests from %s'" +msgid "Pull Requests from '%s'" msgstr "Połączonych gałęzi #%s" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 @@ -6462,27 +6468,6 @@ #~ msgid "reviewer" #~ msgstr "recenzent" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "Twoje hasło zostało zresetowane, nowe hasło zostanie wysłane na e-mail" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new Kallithea password:%s" #~ msgstr "Nowe hasło do strony: %s" @@ -6501,3 +6486,27 @@ #~ msgid "Created by" #~ msgstr "utworzono" +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "Potwierdź unieważnienie pamięci podręcznej repozytorium" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "Komentarze analizowane za pomocą %s składni od %s wsparcia." + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "Podgląd komentarza" + +#~ msgid "Preview" +#~ msgstr "Podgląd" + +#~ msgid "New file mode" +#~ msgstr "Nowy tryb pliku" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2014-02-13 14:34+0000\n" "Last-Translator: marcinkuzminski \n" "Language-Team: Portuguese (Brazil) " @@ -18,12 +18,12 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Não há nenhum changeset ainda" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -35,35 +35,29 @@ msgid "None" msgstr "Nenhum" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(fechado)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "Mostrar espaços em branco" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "Ignorar espaços em branco" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "Mudar o estado de um changeset associado a um pull request não é permitido" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "" @@ -120,10 +114,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "Conjunto de mudanças era grande demais e foi cortado..." @@ -132,111 +126,113 @@ msgid "%s committed on %s" msgstr "%s commitados em %s" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "Clique aqui para adicionar um novo arquivo" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, fuzzy, python-format msgid "%s at %s" msgstr "em %s e %s" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "Este repositório foi travado por %s em %s" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +#, fuzzy +msgid "You can only delete files with revision being a valid branch" +msgstr "Só é possível editar arquivos quando a revisão é um ramo válido" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "Ocorreu um erro ao realizar commit" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " +#: kallithea/controllers/files.py:379 +#, fuzzy +msgid "You can only edit files with revision being a valid branch" msgstr "Só é possível editar arquivos quando a revisão é um ramo válido" -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "Arquivo %s editado via Kallithea" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Sem modificações" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "Commit realizado com sucesso para %s" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Arquivo adicionado via Kallithea" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Nenhum conteúdo" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "Nenhum nome de arquivo" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "O caminho deve ser relativo e não pode conter .." -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "Downloads desabilitados" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Revisão desconhecida %s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Repositório vazio" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "Tipo de arquivo desconhecido" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Conjuntos de mudanças" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "Ramos" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Etiquetas" @@ -250,7 +246,7 @@ msgid "Groups" msgstr "" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -258,23 +254,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Repositórios" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "Ramo" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "Ramos Fechados" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "" @@ -285,167 +285,170 @@ msgstr "Diário Público" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "Diário" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "Você foi registrado no Kallithea com sucesso" - -#: kallithea/controllers/login.py:202 -#, fuzzy -#| msgid "Your password reset link was sent" +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "Você foi registrado no %s com sucesso" + +#: kallithea/controllers/login.py:195 +#, fuzzy msgid "A password reset confirmation code has been sent" msgstr "Seu link de reinicialização de senha foi enviado" -#: kallithea/controllers/login.py:251 -#, fuzzy -#| msgid "Password reset link" +#: kallithea/controllers/login.py:244 +#, fuzzy msgid "Invalid password reset token" msgstr "Link para trocar senha" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, fuzzy, python-format msgid "%s (closed)" msgstr "" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "Conjunto de Mudanças" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "Especial" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "Ramos pares" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Marcadores" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 #, fuzzy msgid "No description" msgstr "Descrição" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "Novo pull request criado com sucesso" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 #, fuzzy msgid "Error occurred while creating pull request" msgstr "Ocorreu um erro durante o envio do pull request" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 #, fuzzy msgid "Pull request update created" msgstr "Revisores do pull request" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 #, fuzzy msgid "Pull request updated" msgstr "Pull requests para %s" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "Pull request excluído com sucesso" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 +#: kallithea/controllers/pullrequests.py:727 #, fuzzy msgid "No permission to change pull request status" msgstr "Vote para estado do pull request" -#: kallithea/controllers/pullrequests.py:727 +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "Pull request excluído com sucesso" + +#: kallithea/controllers/pullrequests.py:748 #, fuzzy msgid "Closing." msgstr "carregando ..." @@ -463,13 +466,13 @@ msgid "An error occurred during search operation." msgstr "Ocorreu um erro durante essa operação de busca" -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 #, fuzzy msgid "No data ready yet" msgstr "Ainda não há dados carregados" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "As estatísticas estão desabillitadas para este repositório" @@ -490,66 +493,66 @@ msgid "Error occurred during update of defaults" msgstr "Ocorreu um erro durnge a atualização dos padrões" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 -#: kallithea/controllers/admin/users.py:285 +#: kallithea/controllers/admin/users.py:284 #, fuzzy msgid "Forever" msgstr "para sempre" -#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/gists.py:59 #: kallithea/controllers/admin/my_account.py:244 -#: kallithea/controllers/admin/users.py:286 +#: kallithea/controllers/admin/users.py:285 msgid "5 minutes" msgstr "cinco minutos" +#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/users.py:286 +msgid "1 hour" +msgstr "uma hora" + #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "uma hora" - -#: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 -#: kallithea/controllers/admin/users.py:288 msgid "1 day" msgstr "um dia" -#: kallithea/controllers/admin/gists.py:63 +#: kallithea/controllers/admin/gists.py:62 #: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 +#: kallithea/controllers/admin/users.py:288 msgid "1 month" msgstr "um mês" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Ocorreu um erro durante a criação de um gist" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "Gist %s excluído" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 #, fuzzy msgid "Unmodified" msgstr "Última alteração" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "" @@ -564,7 +567,7 @@ msgstr "Sua conta foi atualizada com sucesso" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "Ocorreu um erro durante a atualização do usuário %s" @@ -574,33 +577,33 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "Email %s adicionado ao usuário" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "Ocorreu um erro durante o salvamento do email" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "Email removido do usuário" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -650,10 +653,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "Administrador" @@ -684,7 +687,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "Ativação manual de conta externa" @@ -696,7 +699,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "Ativação automática de conta externa" @@ -717,244 +720,244 @@ msgid "Error occurred during update of permissions" msgstr "Ocorreu um erro durante a atualização das permissões" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "Ocorreu um erro durante a criação do grupo de repositórios %s" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "Grupo de repositórios %s criado" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "Grupo de repositórios %s atualizado" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "Ocorreu um erro durante a atualização do grupo de repositórios %s" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "Esse grupo contém %s repositórios e não pode ser excluído" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "Este grupo contém %s subgrupos e não pode ser excluído" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "Grupo de repositórios %s excluído" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "Ocorreu um erro durante a exclusão do grupo de repositórios %s" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "Você não pode revocar sua própria permissão de administrador" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "Permissões atualizadas do Grupo de Repositórios" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "Ocorreu um erro durante a revocação das permissões" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "Erro ao criar repositório %s" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "Repositório %s criado de %s" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "Repositório %s bifurcado como %s" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "Repositório %s criado" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "Repositório %s atualizado com sucesso" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "Ocorreu um erro durante a atualização do repositório %s" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "%s bifurcações excluídas" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "Repositório %s excluído" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, fuzzy, python-format msgid "Cannot delete repository %s which still has forks" msgstr "Nao é possível excluir %s pois ele ainda contém bifurcações vinculadas" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "Ocorreu um erro durante a exclusão de %s" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "Permissões do repositório atualizadas" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "Ocorreu um erro durante a criação do campo" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "Ocorreu um erro durante a remoção do campo" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "Atualizada a visibilidade do repositório no diário público" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 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" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "Nada" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "Marcado repositório %s como bifurcação de %s" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "Ocorreu um erro durante essa operação" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "Repositório não está travado" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "Repositório não está travado" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "Ocorreu um erro durante o destravamento" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "Ocorreu um erro ao invalidar o cache" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "Realizado pull de localização remota" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "Ocorreu um erro ao realizar pull de localização remota" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "Ocorreu um erro ao excluir estatísticas de repositório" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "Configurações de VCS atualizadas" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "Ocorreu um erro durante a atualização das configurações da aplicação" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, fuzzy, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "Repositórios varridos com sucesso adicionados: %s ; removidos: %s" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "Configurações da aplicação atualizadas" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "Configurações de visualização atualizadas" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "Ocorreu um erro durante a atualização das configurações de visualização" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "Adicionado novo gancho" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "Atualizados os ganchos" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "Ocorreu um erro durante a criação do hook" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "Tarefa de reindexação do whoosh agendada" @@ -995,76 +998,80 @@ msgstr "Permissões do Grupo de Usuários atualizadas" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "Permissões atualizadas" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "Ocorreu um erro durante o salvamento das permissões" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "Usuário %s criado" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "Ocorreu um erro durante a criação do usuário %s" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "Usuário atualizado com sucesso" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "Usuário excluído com sucesso" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "Ocorreu um erro ao excluir o usuário" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "Ocorreu um erro durante o salvamento do IP" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "IP %s não permitido" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 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" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "Você precisa estar logado para ver essa página" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "Conjunto de alterações não encontrado" @@ -1082,126 +1089,126 @@ msgid "No changes detected" msgstr "Nenhuma alteração detectada" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "Excluído ramo: %s" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "Tag criada: %s" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "Ver todos os conjuntos de mudanças combinados %s->%s" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 #, fuzzy msgid "Compare view" msgstr "comparar exibir" -#: kallithea/lib/helpers.py:696 +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "e" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "%s mais" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "revisões" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, fuzzy, python-format msgid "Fork name %s" msgstr "nome da bifurcação %s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, fuzzy, python-format msgid "Pull request %s" msgstr "Pull request #%s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "repositório [excluído]" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "repositório [criado]" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "repositório [criado] como uma bifurcação" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "repositório [bifurcado]" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "repositório [atualizado]" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "[baixado] archive do repositório" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "[excluir] repositório" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "usuário [criado]" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "usuário [atualizado]" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "[criado] grupo de usuários" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "[atualizado] grupo de usuários" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "[comentado] em revisão no repositório" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "[comentado] no pull request para" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "[fechado] pull request para" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[realizado push] para" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "[commitado via Kallithea] no repositório" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "[pulled do remote] no repositório" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "[realizado pull] a partir de" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "[passou a seguir] o repositório" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "[parou de seguir] o repositório" @@ -1211,8 +1218,8 @@ msgstr " e mais %s" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "Nenhum arquivo" @@ -1236,7 +1243,7 @@ msgid "chmod" msgstr "chmod" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1247,69 +1254,69 @@ "renomeado a partir do sistema de arquivos. Por favor, execute a aplicação" " outra vez para varrer novamente por repositórios" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "%d ano" msgstr[1] "%d anos" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d mês" msgstr[1] "%d meses" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d dia" msgstr[1] "%d dias" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d hora" msgstr[1] "%d horas" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "%d segundo" msgstr[1] "%d segundos" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "em %s" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "%s atrás" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "em %s e %s" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "%s e %s atrás" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "agora há pouco" @@ -1408,7 +1415,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Administrador do Kallithea" @@ -1519,7 +1526,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "Aprovado" @@ -1534,7 +1541,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "Rejeitado" @@ -1561,7 +1568,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "nível superior" @@ -1708,7 +1715,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "Registro desatilitado" @@ -1735,12 +1742,12 @@ msgstr "Registro de Usuário com ativação automática de conta" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1749,114 +1756,114 @@ msgid "on line %s" msgstr "na linha %s" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[Menção]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 +#: kallithea/model/db.py:1672 #, fuzzy msgid "Default user has read access to new repositories" msgstr "Acesso não autorizado ao recurso" -#: kallithea/model/db.py:1669 +#: kallithea/model/db.py:1673 #, fuzzy msgid "Default user has write access to new repositories" msgstr "Acesso não autorizado ao recurso" -#: kallithea/model/db.py:1670 +#: kallithea/model/db.py:1674 msgid "Default user has admin access to new repositories" msgstr "" -#: kallithea/model/db.py:1672 +#: kallithea/model/db.py:1676 msgid "Default user has no access to new repository groups" msgstr "" -#: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" -msgstr "" - #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 +msgid "Default user has read access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1683 +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1686 #, fuzzy msgid "Only admins can create repository groups" msgstr "Grupo de repositórios %s criado" -#: kallithea/model/db.py:1683 +#: kallithea/model/db.py:1687 #, fuzzy msgid "Non-admins can create repository groups" msgstr "Grupo de repositórios %s criado" -#: kallithea/model/db.py:1685 +#: kallithea/model/db.py:1689 #, fuzzy msgid "Only admins can create user groups" msgstr "Criar grupos de usuários" -#: kallithea/model/db.py:1686 +#: kallithea/model/db.py:1690 #, fuzzy msgid "Non-admins can create user groups" msgstr "Criar grupos de usuários" -#: kallithea/model/db.py:1688 +#: kallithea/model/db.py:1692 msgid "Only admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1689 +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 #, fuzzy msgid "Only admins can fork repositories" msgstr "Criar repositórios" -#: kallithea/model/db.py:1695 -#, fuzzy -msgid "Non-admins can can fork repositories" +#: kallithea/model/db.py:1699 +#, fuzzy +msgid "Non-admins can fork repositories" msgstr "Invalidar o cache para todos os repositórios" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 #, fuzzy msgid "User registration with manual account activation" msgstr "Registro de Usuário com ativação manual de conta" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 #, fuzzy msgid "User registration with automatic account activation" msgstr "Registro de Usuário com ativação automática de conta" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "Não Revisado" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "Sob Revisão" @@ -1879,7 +1886,7 @@ msgid "Enter %(min)i characters or more" msgstr "Entre com %(min)i caracteres ou mais" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1955,14 +1962,11 @@ #: kallithea/model/notification.py:307 #, fuzzy, python-format -#| msgid "%(user)s wants you to review pull request %(pr_nice_id)s: -#| %(pr_title)s" msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "%(user)s solicita sua revisão no pull request $%(pr_id)s: %(pr_title)s" #: kallithea/model/notification.py:308 #, fuzzy, python-format -#| msgid "[commented] on pull request for" msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "[comentado] no pull request para" @@ -1976,7 +1980,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "%(user)s solicita sua revisão no pull request $%(pr_id)s: %(pr_title)s" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "tip mais recente" @@ -2018,17 +2022,16 @@ "usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. " "Troque os donos ou remova esses repositórios. %s" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "Link para trocar senha" -#: kallithea/model/user.py:408 -#, fuzzy -#| msgid "Password reset link" +#: kallithea/model/user.py:418 +#, fuzzy msgid "Password reset notification" msgstr "Link para trocar senha" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -2039,17 +2042,17 @@ msgid "Value cannot be an empty list" msgstr "O valor não pode ser uma lista vazia" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "O username \\\"%(username)s\\\" já existe" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, fuzzy, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "O username \"%(username)s\" não é válido" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 #, fuzzy msgid "" "Username may only contain alphanumeric characters underscores, periods or" @@ -2058,25 +2061,25 @@ "Nome de usuário pode conter somente caracteres alfanuméricos, sublinha, " "pontos e hífens e deve iniciar com caractere alfanumérico" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "O username \"%(username)s\" não é válido" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "Nome inválido de grupo de usuários" -#: kallithea/model/validators.py:153 +#: kallithea/model/validators.py:155 #, python-format msgid "User group \"%(usergroup)s\" already exists" msgstr "O grupo de usuários \"%(usergroup)s\" já existe" -#: kallithea/model/validators.py:155 +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" @@ -2085,107 +2088,107 @@ "underscores, pontos ou hífens, e deve começar om um caractere alfa-" "numérico" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "Não é possível associar esse grupo como progenitor" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "O grupo \\\"%(group_name)s\\\" já existe" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "Um repositório com o nome \"%(group_name)s\" já existe" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "Caracteres inválidos (não-ascii) na senha" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "Senhas não conferem" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 #, fuzzy msgid "Invalid username or password" msgstr "senha inválida" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "Descompasso de Token" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, fuzzy, python-format msgid "Repository name %(repo)s is not allowed" msgstr "O nome de repositório %(repo)s não é permitido" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "Um repositório chamado %(repo)s já existe" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "Um repositório \"%(repo)s\" já existe no grupo \"%(group)s\"" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "Um Grupo de Repositórios chamado \"%(repo)s\" já existe" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy msgid "Invalid repository URL" msgstr "repositório privado" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "A bifurcação deve ser do mesmo tipo que o pai" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "Você não tem permissão para criar um repositório neste grupo" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "você não tem permissão para criar um repositório na raiz" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "Você não tem permissão para criar um grupo neste local" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "Este nome de usuário ou de grupo de usuários não é válido" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "Esse não é um caminho válido" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 #, fuzzy msgid "This email address is already in use" msgstr "Esse endereço de e-mail já está tomado" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, fuzzy, python-format msgid "Email address \"%(email)s\" not found" msgstr "o e-mail \"%(email)s\" não existe." -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" @@ -2193,24 +2196,24 @@ "O atributo de login LDAP do CN deve ser especificado - isto é o nome do " "atributo que é equivalente ao 'nome de usuário'" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "Por favor, forneça um endereço válido IPv4 ou IPv6" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "O tamanho da rede (bits) deve estar no intervalo 0-32 (não %(bits)r)" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "O nome da chave só pode conter letras, underscore, hífen ou dígitos" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "O nome de arquivo não pode estar dentro de um diretório" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2337,7 +2340,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "Dono" @@ -2385,7 +2388,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2395,7 +2398,7 @@ msgstr "Carregando..." #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "Entrar" @@ -2410,7 +2413,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "Nome de usuário" @@ -2418,7 +2421,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "Senha" @@ -2430,7 +2433,7 @@ msgid "Forgot your password ?" msgstr "Esqueceu sua senha ?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "Não possui uma conta ?" @@ -2471,8 +2474,6 @@ #: kallithea/templates/password_reset.html:47 #, fuzzy -#| msgid "" "Password reset link will be sent to the email address matching -#| your " "username." msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." @@ -2497,13 +2498,11 @@ #: kallithea/templates/password_reset_confirmation.html:39 #, fuzzy -#| msgid "New password" msgid "New Password" msgstr "Nova senha" #: kallithea/templates/password_reset_confirmation.html:48 #, fuzzy -#| msgid "Your new password" msgid "Confirm New Password" msgstr "Sua nova senha" @@ -2562,10 +2561,6 @@ msgid "There are no branches yet" msgstr "Ainda não há ramos" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "Ramos Fechados" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2798,12 +2793,12 @@ msgid "Never" msgstr "nunca" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "Cancelar" @@ -2826,7 +2821,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2914,7 +2909,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2934,8 +2930,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2962,13 +2956,12 @@ msgstr "criado" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "Mostrar original" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "Minha Conta" @@ -3159,7 +3152,7 @@ msgstr "Comentários" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "Pull Requests" @@ -3179,7 +3172,7 @@ msgstr "Mostrar notificação" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "Notificações" @@ -3392,7 +3385,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3680,6 +3673,11 @@ msgid "Unlock Repository" msgstr "Repositório público" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 #, fuzzy msgid "Confirm to lock repository." @@ -3740,11 +3738,6 @@ msgid "Invalidate Repository Cache" msgstr "Invalidar cache do repositório" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -#, fuzzy -msgid "Confirm to invalidate repository cache." -msgstr "Confirma invalidar cache do repositório" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 #, fuzzy msgid "" @@ -4506,22 +4499,18 @@ msgid "Files" msgstr "Arquivos" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "Trocar Para" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "Opções" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 #, fuzzy msgid "Compare Fork" msgstr "Compare bifurcação" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4531,117 +4520,122 @@ msgid "Compare" msgstr "Compare" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "Pesquisar" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "Destravar" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "Travar" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "Seguir" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "Parar de seguir" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "Parar de seguir" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "Bifurcação" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "Criar Pull Request" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "Mostrar Pull Requests para %s" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "Trocar Para" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "Mostrar atividade recente" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "Diário público" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "Mostrar gists públicos" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "Gists" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 #, fuzzy msgid "All Public Gists" msgstr "Todos os gists públicos" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 #, fuzzy msgid "My Public Gists" msgstr "Meus gists públicos" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 #, fuzzy msgid "My Private Gists" msgstr "Meus gists privados" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "Buscar nos repositórios" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 #, fuzzy msgid "My Pull Requests" msgstr "Pull requests" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 #, fuzzy msgid "Not Logged In" msgstr "Não logado" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 #, fuzzy msgid "Login to Your Account" msgstr "Entrar com sua conta" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "Esqueceu a senha ?" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "Sair" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4750,7 +4744,6 @@ #: kallithea/templates/base/root.html:31 #, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "Comentar no pull request #%s" @@ -4760,7 +4753,6 @@ #: kallithea/templates/base/root.html:33 #, fuzzy -#| msgid "Show Selected Changesets __S → __E" msgid "Show Selected Changesets {0} → {1}" msgstr "Mostrar changesets selecionados __S -> __E" @@ -4771,6 +4763,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 #, fuzzy msgid "Collapse Diff" msgstr "Colapsar diff" @@ -4884,54 +4877,58 @@ #: kallithea/templates/changelog/changelog.html:92 #: kallithea/templates/changelog/changelog_summary_data.html:20 #, fuzzy, python-format +#| msgid "" "Changeset status: %s\n" "Click to open associated pull request %s" msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" "Estado do changeset: %s\n" "Clique para abrir os pull request #%s associado" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Changeset status: %s" +msgid "Changeset status: %s by %s" msgstr "Estado do changeset: %s" -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "O changeset tem comentários" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "Bookmark %s" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "Tag %s" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "Ramo %s" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "Ainda não há alteações" @@ -4947,7 +4944,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "Adicionado" @@ -4977,22 +4974,22 @@ msgid "Refs" msgstr "Refs" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "Adicionar ou enviar arquivos diretamente pelo Kallithea" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy msgid "Push new repository" msgstr "Fazer push de novo repositório" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "Repositório existente?" @@ -5010,13 +5007,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "Estado do changeset" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "Diff cru" @@ -5026,7 +5023,7 @@ msgstr "D" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "Baixar diff" @@ -5057,8 +5054,8 @@ msgstr "criado" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -5066,8 +5063,8 @@ msgstr[1] "%s arquivos modificados" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -5076,14 +5073,14 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 #, fuzzy msgid "Show full diff anyway" msgstr "Mostrar diff completo" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy msgid "No revisions" msgstr "revisões" @@ -5103,112 +5100,96 @@ msgid "on this changeset" msgstr "Nenhum changeset" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 #, fuzzy msgid "Delete comment?" msgstr "%d comentário" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "Mudanças mais recentes" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +#, fuzzy +msgid "Commenting on line." msgstr "Comentando a linha {1}." #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "Comentários interpretados usando a sintaxe %s com suporte a %s." - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -#, fuzzy -msgid "Use @username inside this text to notify another user" +#, fuzzy +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." msgstr "" "Use @nomedeusuário dentro desse texto para enviar notificação a este " "usuário do Kallithea" -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "Visualizar comentário" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +#: kallithea/templates/changeset/changeset_file_comment.html:67 +#, fuzzy +msgid "Set changeset status" +msgstr "Altere o estado do changeset" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "Vote para estado do pull request" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +#, fuzzy +msgid "No change" +msgstr "Sem modificações" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Comentar no pull request #%s" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +#, fuzzy +msgid "Close" +msgstr "(fechado)" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 #, fuzzy msgid "Submitting ..." msgstr "Enviando..." -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "Comentário" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "Visualizar" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "Você precisa estar logado para comentar." -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "Entrar agora" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "Ocultar" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "%d comentário" msgstr[1] "%d comentários" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, fuzzy, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "(%d em linha)" msgstr[1] "(%d em linha)" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, fuzzy, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "" msgstr[1] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -#, fuzzy -msgid "Use @username inside this text to notify another user." -msgstr "" -"Use @nomedeusuário dentro desse texto para enviar notificação a este " -"usuário do Kallithea" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "Vote para estado do pull request" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -#, fuzzy -msgid "Set changeset status" -msgstr "Altere o estado do changeset" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -#, fuzzy -msgid "No change" -msgstr "Sem modificações" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -#, fuzzy -msgid "Close" -msgstr "(fechado)" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5218,31 +5199,30 @@ msgid "Files affected" msgstr "Arquivos afetados" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +#, fuzzy +msgid "Deleted" +msgstr "excluir" + +#: kallithea/templates/changeset/diff_block.html:57 +#, fuzzy +msgid "Renamed" +msgstr "renomear" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "Mostrar diff completo para este arquivo" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "Mostrar diff completo lado-a-lado para este arquivo" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "Mostrar comentários inline" -#: kallithea/templates/changeset/diff_block.html:86 -#, fuzzy -msgid "Deleted" -msgstr "excluir" - -#: kallithea/templates/changeset/diff_block.html:89 -#, fuzzy -msgid "Renamed" -msgstr "renomear" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "Nenhum changeset" @@ -5251,6 +5231,11 @@ msgid "Ancestor" msgstr "Antecessor" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "Estado do changeset: %s" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5263,32 +5248,32 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 #, fuzzy msgid "Show merge diff" msgstr "Mostrar diff completo" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 #, fuzzy msgid "Common ancestor" msgstr "Comentário no changeset" -#: kallithea/templates/compare/compare_cs.html:90 +#: kallithea/templates/compare/compare_cs.html:99 msgid "No common ancestor found - repositories are unrelated" msgstr "" -#: kallithea/templates/compare/compare_cs.html:98 +#: kallithea/templates/compare/compare_cs.html:107 #, fuzzy msgid "is" msgstr "Gist" -#: kallithea/templates/compare/compare_cs.html:99 +#: kallithea/templates/compare/compare_cs.html:108 #, fuzzy, python-format msgid "%s changesets" msgstr "%s Changesets" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 #, fuzzy msgid "behind" msgstr "Reindexar" @@ -5300,28 +5285,28 @@ msgstr "%s Comparar" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "Mostrando %s commit" msgstr[1] "Mostrando %s commits" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "Mostrar diff completo" @@ -5378,21 +5363,26 @@ #: kallithea/templates/email_templates/password_reset.html:6 #, fuzzy -#| msgid "We received a request to create a new password for your account." msgid "We have received a request to reset the password for your account." msgstr "Recebemos uma requisição para criar uma nova senha para sua conta." -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5477,8 +5467,9 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "" +#, fuzzy +msgid "New file type" +msgstr "novo arquivo" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5613,10 +5604,21 @@ msgid "Binary file (%s)" msgstr "Arquivo binário (%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +#, fuzzy +msgid "File is too big to display." msgstr "Arquivo é grande demais para exibir" +#: kallithea/templates/files/files_source.html:76 +#, fuzzy +msgid "Show full annotation anyway." +msgstr "Mostrar diff completo" + +#: kallithea/templates/files/files_source.html:78 +#, fuzzy +msgid "Show as raw." +msgstr "Mostrar original" + #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" msgstr "anotação" @@ -5886,44 +5888,50 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 #, fuzzy msgid "Pull Request Reviewers" msgstr "Revisores do pull request" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 #, fuzzy msgid "Remove reviewer" msgstr "revisor" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 +#: kallithea/templates/pullrequests/pullrequest_show.html:261 msgid "Type name of reviewer to add" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:258 +#: kallithea/templates/pullrequests/pullrequest_show.html:269 #, fuzzy msgid "Potential Reviewers" msgstr "Visualizar comentário" -#: kallithea/templates/pullrequests/pullrequest_show.html:261 +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -#, fuzzy -msgid "Save as New Pull Request" +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#, fuzzy +msgid "Save Updates as New Pull Request" msgstr "Crie novo pull request" -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:297 #, fuzzy msgid "Cancel Changes" msgstr "Mudanças mais recentes" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 #, fuzzy msgid "Pull Request Content" msgstr "O pull request mudou de estado" @@ -5935,7 +5943,7 @@ #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 #, fuzzy, python-format -msgid "Pull Requests from %s'" +msgid "Pull Requests from '%s'" msgstr "Pull requests de %s" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 @@ -6443,30 +6451,6 @@ #~ msgid "reviewer" #~ msgstr "revisor" -#~ 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" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new Kallithea password:%s" #~ msgstr "Sua nova senha no Kallithea: %s" @@ -6485,3 +6469,27 @@ #~ msgid "Created by" #~ msgstr "criado" +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "Confirma invalidar cache do repositório" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "Comentários interpretados usando a sintaxe %s com suporte a %s." + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "Visualizar comentário" + +#~ msgid "Preview" +#~ msgstr "Visualizar" + +#~ msgid "New file mode" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/ru/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/ru/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/ru/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -12,14 +12,14 @@ # SkryabinD , 2014 # softforwinxp , 2013 # zhmylove , 2013 -# Andrew Shadura , 2015 +# Andrew Shadura , 2015, 2016. # msgid "" msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" -"PO-Revision-Date: 2015-04-13 20:18+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" +"PO-Revision-Date: 2016-02-22 19:33+0100\n" "Last-Translator: Andrew Shadura \n" "Language-Team: Russian " "\n" @@ -31,12 +31,12 @@ "4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 2.3-dev\n" -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Ещё не было изменений" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -48,37 +48,29 @@ msgid "None" msgstr "Ничего" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(закрыто)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "Отображать пробелы" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "Игнорировать пробелы" -#: kallithea/controllers/changeset.py:169 -#, fuzzy, python-format +#: kallithea/controllers/changeset.py:168 +#, python-format msgid "Increase diff context to %(num)s lines" -msgstr "увеличить контекст до %(num)s строк" - -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +msgstr "Увеличить контекст до %(num)s строк" + +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "Нет такой ревизии в этом репозитории" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" -"Нельзя редактировать статус изменений, связанных с закрытыми pull-" -"request'ами" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "Выбрать набор изменений" @@ -88,13 +80,12 @@ msgstr "Невозможно сравнивать репозитории без общего предка" #: kallithea/controllers/error.py:71 -#, fuzzy msgid "No response" -msgstr "версии" +msgstr "Нет ответа" #: kallithea/controllers/error.py:72 msgid "Unknown error" -msgstr "" +msgstr "Неизвестная ошибка" #: kallithea/controllers/error.py:100 msgid "The request could not be understood by the server due to malformed syntax." @@ -131,10 +122,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "Изменения оказались слишком большими и были вырезаны..." @@ -143,115 +134,115 @@ msgid "%s committed on %s" msgstr "%s выполнил коммит в %s" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "Нажмите чтобы добавить новый файл" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "Нет файлов. %s" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "%s (%s)" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "Репозиторий заблокировал %s в %s" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" msgstr "Вы можете удалять файлы только в ревизии, связанной с существующей веткой " -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "Файл %s удалён с помощью Kallithea" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "Файл %s удалён" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "Во время коммита произошла ошибка" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" msgstr "" "Вы можете редактировать файлы только в ревизии, связанной с существующей " "веткой " -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "Файл %s отредактирован с помощью Kallithea" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Без изменений" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "Изменения применены в %s" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Файл добавлен с помощью Kallithea" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Пусто" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "Безымянный" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" "Расположение должно быть относительным путем, и не должно содержать " "\"..\" в пути" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "Возможность скачивать отключена" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Неизвестная ревизия %s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Пустой репозиторий" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "Неизвестный тип архива" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Набор изменений" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "Ветки" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Метки" @@ -265,7 +256,7 @@ msgid "Groups" msgstr "Группы" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -273,23 +264,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Репозитории" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "Ветка" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "Закрытые ветки" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "Тэги" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "Закладки" @@ -300,163 +295,163 @@ msgstr "Публичный журнал" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "Журнал" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 -#, fuzzy +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" -msgstr "неверная капча" - -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "Регистрация в Kallithea прошла успешно" - -#: kallithea/controllers/login.py:202 -#, fuzzy -#| msgid "Your password reset link was sent" +msgstr "Неверная капча" + +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "Регистрация в %s прошла успешно" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" -msgstr "Ссылка для сброса пароля отправлена" - -#: kallithea/controllers/login.py:251 -#, fuzzy -#| msgid "Password reset link" +msgstr "Код для сброса пароля отправлен" + +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" -msgstr "Ссылка сброса пароля" - -#: kallithea/controllers/login.py:256 +msgstr "Неверный код для сброса пароля" + +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "Пароль обновлён" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (закрыта)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "Изменения" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "Специальный" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "Ветки участника" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Закладки" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "Ошибка при создании pull-запроса: %s" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "Нет описания" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "Pull-запрос создан успешно" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "Произошла ошибка при создании pull-запроса" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "Отсутствующие ревизии относительно предыдущего pull-запроса:" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "Новые ревизии на %s %s относительно предыдущего pull-запроса" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "Этот pull-запрос основан на другой ревизии %s, простой diff невозможен" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "Нет изменений на %s %s относительно предыдущей версии." -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "Закрыт, замещён %s ." -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "Обновление для pull-запроса создано" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "Pull-запрос обновлён" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "Pull-запрос успешно удалён" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "Этот pull-запрос уже принят на ветку %s." -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "Этот pull-запрос был закрыт и не может быть обновлён." -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "Этот pull-запрос может быть обновлён из %s:" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "Нет изменений для обновления этого pull-запроса." -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "Внимание: Ветка %s имеет ещё одну верхушку: %s." -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "Обновление pull-запросы git не поддерживается." -#: kallithea/controllers/pullrequests.py:722 +#: kallithea/controllers/pullrequests.py:727 msgid "No permission to change pull request status" msgstr "" -#: kallithea/controllers/pullrequests.py:727 +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "Pull-запрос успешно удалён" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "Закрыт." @@ -472,12 +467,12 @@ msgid "An error occurred during search operation." msgstr "Произошла ошибка при выполнении этого поиска." -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "Нет данных" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "Статистические данные отключены для этого репозитария" @@ -498,66 +493,66 @@ msgid "Error occurred during update of defaults" msgstr "Произошла ошибка при обновлении стандартных настроек" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 -#: kallithea/controllers/admin/users.py:285 +#: kallithea/controllers/admin/users.py:284 #, fuzzy msgid "Forever" msgstr "навсегда" -#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/gists.py:59 #: kallithea/controllers/admin/my_account.py:244 -#: kallithea/controllers/admin/users.py:286 +#: kallithea/controllers/admin/users.py:285 msgid "5 minutes" msgstr "5 минут" +#: kallithea/controllers/admin/gists.py:60 +#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/users.py:286 +msgid "1 hour" +msgstr "1 час" + #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "1 час" - -#: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 -#: kallithea/controllers/admin/users.py:288 msgid "1 day" msgstr "1 день" -#: kallithea/controllers/admin/gists.py:63 +#: kallithea/controllers/admin/gists.py:62 #: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 +#: kallithea/controllers/admin/users.py:288 msgid "1 month" msgstr "1 месяц" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "Срок" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Произошла ошибка во время создания gist-записи" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "Gist-запись %s удалена" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 #, fuzzy msgid "Unmodified" msgstr "Последнее изменение" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "Данные gist-записи обновлены" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "Произошла ошибка при обновлении gist-записи %s" @@ -574,7 +569,7 @@ msgstr "Ваша учетная запись успешно обновлена" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "Произошла ошибка при обновлении пользователя %s" @@ -584,33 +579,33 @@ msgstr "Ошибка при обновлении пароля" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "Пользователю добавлен e-mail %s" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "Произошла ошибка при сохранении e-mail" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "E-mail пользователя удалён" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "API-ключ успешно создан" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "API-ключ успешно сброшен" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "API-ключ успешно удалён" @@ -660,10 +655,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "Администратор" @@ -694,7 +689,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "Ручная активация внешней учетной записи" @@ -706,7 +701,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "Автоматическая активация внешней учетной записи" @@ -727,196 +722,196 @@ msgid "Error occurred during update of permissions" msgstr "Произошла ошибка во время обновления привилегий" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "Произошла ошибка при создании группы репозиториев %s" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "Создана новая группа репозиториев %s" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "Группа репозиториев %s обновлена" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "Произошла ошибка при обновлении группы репозиториев %s" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "Данная группа содержит %s репозитариев и не может быть удалена" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "Группа содержит в себе %s подгрупп и не может быть удалён" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "Группа репозиториев %s удалена" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "Произошла ошибка при удалении группы репозиториев %s" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "Администратор не может отозвать свои привелегии" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "Привилегии группы репозиториев обновлены" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "Произошла ошибка при отзыве привелегии" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "Произошла ошибка при создании репозитория %s" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "Репозиторий %s создан из %s" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "Сделан форк(копия) репозитория %s на %s" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "Репозиторий %s создан" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "Репозитарий %s успешно обновлён" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "Произошла ошибка во время обновления репозитория %s" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "Форки %s отсоединены" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "Удалены форки репозитория %s" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "Репозиторий %s удалён" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, fuzzy, python-format msgid "Cannot delete repository %s which still has forks" msgstr "Невозможно удалить %s, он всё-ещё содержит форки" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "Произошла ошибка во время удаления %s" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "Привилегии репозитория обновлены" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "Произошла ошибка при создании поля" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "Произошла ошибка при удалении поля" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "-- Не форк --" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "Видимость репозитория в публичном журнале обновлена" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "Произошла ошибка при установке репозитария в общедоступный журнал" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "Ничего" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "Репозиторий %s отмечен как форк %s" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "Произошла ошибка при выполнении операции" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "Репозиторий не заблокирован" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "Репозиторий не заблокирован" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "Произошла ошибка во время разблокирования" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "Кэш сброшен" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "Произошла ошибка при очистке кэша" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "Внесены изменения из удалённого репозитория" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "Произошла ошибка при внесении изменений из удалённого репозитория" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "Произошла ошибка при удалении статистики репозитория" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "Обновлены настройки VCS" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" @@ -924,49 +919,49 @@ "Невозможно включить поддержку hgsubversion. Библиотека «hgsubversion» " "отсутствует" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "Произошла ошибка при обновлении настроек приложения" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "Репозитории успешно пересканированы, добавлено: %s, удалено: %s." -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "Обновленные параметры настройки приложения" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "Настройки визуализации обновлены" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "Произошла ошибка при обновлении настроек визуализации" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "Пожалуйста, введите адрес электронной почты" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "Задача отправки Email создана" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "Добавлена новая ловушка" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "Обновлённые ловушки" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "произошла ошибка при создании хука" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "Запланирована переиндексация базы Whoosh" @@ -1007,78 +1002,82 @@ msgstr "Привилегии группы пользователей обновлены" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "Обновлены привилегии" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "Произошла ошибка при сохранении привилегий" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "Пользователь %s создан" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "Произошла ошибка при создании пользователя %s" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "Пользователь успешно обновлён" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "Пользователь успешно удалён" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "Произошла ошибка при удалении пользователя" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "Добавлен IP %s в белый список пользователя" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "Произошла ошибка при сохранении IP" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "Удален IP %s из белого списка пользователя" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "IP %s заблокирован" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "" "Вы должны быть зарегистрированным пользователем, чтобы выполнить это " "действие" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "Страница доступна только авторизованным пользователям" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "Репозиторий не найден на файловой системе" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "Набор изменений не найден" @@ -1096,126 +1095,126 @@ msgid "No changes detected" msgstr "Изменений не обнаружено" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "Удалена ветка: %s" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "Создан тег: %s" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "Показать отличия вместе %s->%s" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 #, fuzzy msgid "Compare view" msgstr "сравнение" -#: kallithea/lib/helpers.py:696 +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "и" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "на %s больше" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "версии" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, fuzzy, python-format msgid "Fork name %s" msgstr "имя форка %s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, fuzzy, python-format msgid "Pull request %s" msgstr "Pull-запрос #%s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "[удален] репозиторий" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "[создан] репозиторий" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "[создан] репозиторий как форк" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "[форкнут] репозиторий" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "[обновлён] репозиторий" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "[загружен] архив из репозитория" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "[удален] репозиторий" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "[создан] пользователь" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "[обновлён] пользователь" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "[создана] группа пользователей" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "[обновлена] группа пользователей" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "[комментарий] к ревизии в репозитории" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "[прокомментировано] в запросе на внесение изменений для" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "[закрыт] Pull-запрос для" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[отправлено] в" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "[внесены изменения с помощью Kallithea] в репозитории" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "[внесены изменения из удалённого репозитория] в репозиторий" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "[внесены изменения] из" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "[добавлен в наблюдения] репозиторий" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "[удалён из наблюдения] репозиторий" @@ -1225,8 +1224,8 @@ msgstr " и на %s больше" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "Нет файлов" @@ -1250,7 +1249,7 @@ msgid "chmod" msgstr "chmod" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1261,7 +1260,7 @@ "переименован из файловой системы. Пожалуйста, перезапустите приложение " "для сканирования репозиториев" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" @@ -1269,7 +1268,7 @@ msgstr[1] "%d лет" msgstr[2] "%d года" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" @@ -1277,7 +1276,7 @@ msgstr[1] "%d месяца" msgstr[2] "%d месяцев" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" @@ -1285,7 +1284,7 @@ msgstr[1] "%d дня" msgstr[2] "%d дней" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" @@ -1293,7 +1292,7 @@ msgstr[1] "%d часов" msgstr[2] "%d часа" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" @@ -1301,7 +1300,7 @@ msgstr[1] "%d минут" msgstr[2] "%d минуты" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" @@ -1309,27 +1308,27 @@ msgstr[1] "%d секунды" msgstr[2] "%d секунды" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "в %s" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "%s назад" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "в %s и %s" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "%s и %s назад" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "прямо сейчас" @@ -1428,7 +1427,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Администратор Kallithea" @@ -1539,7 +1538,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "Одобрено" @@ -1554,7 +1553,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "Отклонено" @@ -1581,7 +1580,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "верхний уровень" @@ -1728,7 +1727,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "Регистрация отключена" @@ -1755,12 +1754,12 @@ msgstr "Регистрация пользователя с автоматической активацией" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1769,114 +1768,114 @@ msgid "on line %s" msgstr "на строке %s" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[Упоминание]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 +#: kallithea/model/db.py:1672 #, fuzzy msgid "Default user has read access to new repositories" msgstr "Несанкционированный доступ к ресурсу" -#: kallithea/model/db.py:1669 +#: kallithea/model/db.py:1673 #, fuzzy msgid "Default user has write access to new repositories" msgstr "Несанкционированный доступ к ресурсу" -#: kallithea/model/db.py:1670 +#: kallithea/model/db.py:1674 msgid "Default user has admin access to new repositories" msgstr "" -#: kallithea/model/db.py:1672 +#: kallithea/model/db.py:1676 msgid "Default user has no access to new repository groups" msgstr "" -#: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" -msgstr "" - #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 +msgid "Default user has read access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1683 +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1686 #, fuzzy msgid "Only admins can create repository groups" msgstr "Создана новая группа репозиториев %s" -#: kallithea/model/db.py:1683 +#: kallithea/model/db.py:1687 #, fuzzy msgid "Non-admins can create repository groups" msgstr "Создана новая группа репозиториев %s" -#: kallithea/model/db.py:1685 +#: kallithea/model/db.py:1689 #, fuzzy msgid "Only admins can create user groups" msgstr "Создавать группы пользователей" -#: kallithea/model/db.py:1686 +#: kallithea/model/db.py:1690 #, fuzzy msgid "Non-admins can create user groups" msgstr "Создавать группы пользователей" -#: kallithea/model/db.py:1688 +#: kallithea/model/db.py:1692 msgid "Only admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1689 +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 #, fuzzy msgid "Only admins can fork repositories" msgstr "Местонахождение репозиториев" -#: kallithea/model/db.py:1695 +#: kallithea/model/db.py:1699 #, fuzzy -msgid "Non-admins can can fork repositories" +msgid "Non-admins can fork repositories" msgstr "Сбросить кэш для всех репозиториев" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 #, fuzzy msgid "User registration with manual account activation" msgstr "Регистрация пользователя с ручной активацией учётной записи" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 #, fuzzy msgid "User registration with automatic account activation" msgstr "Регистрация пользователя с автоматической активацией" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "Не просмотрено" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "На рассмотрении" @@ -1899,7 +1898,7 @@ msgid "Enter %(min)i characters or more" msgstr "Введите не менее %(min)i символов" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1975,14 +1974,11 @@ #: kallithea/model/notification.py:307 #, fuzzy, python-format -#| msgid "%(user)s wants you to review pull request %(pr_nice_id)s: -#| %(pr_title)s" msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "%(user)s просит вас рассмотреть pull request #%(pr_id)s: %(pr_title)s" #: kallithea/model/notification.py:308 #, fuzzy, python-format -#| msgid "[commented] on pull request for" msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "[прокомментировано] в запросе на внесение изменений для" @@ -1995,7 +1991,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "%(user)s просит вас рассмотреть pull request #%(pr_id)s: %(pr_title)s" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "последняя версия" @@ -2039,17 +2035,16 @@ "поэтому не может быть удалён. Смените владельца или удалите данные " "группы: %s" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "Ссылка сброса пароля" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 #, fuzzy -#| msgid "Password reset link" msgid "Password reset notification" msgstr "Ссылка сброса пароля" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -2060,17 +2055,17 @@ msgid "Value cannot be an empty list" msgstr "Значение не может быть пустым списком" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "Пользователь с именем \"%(username)s\" уже существует" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, fuzzy, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "Имя \"%(username)s\" недопустимо" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 #, fuzzy msgid "" "Username may only contain alphanumeric characters underscores, periods or" @@ -2080,25 +2075,25 @@ "подчеркивания, точки и тире; а так же должно начинаться с буквы, цифры " "либо с символа подчеркивания" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "Имя \"%(username)s\" недопустимо" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "Неверное имя группы пользователей" -#: kallithea/model/validators.py:153 +#: kallithea/model/validators.py:155 #, python-format msgid "User group \"%(usergroup)s\" already exists" msgstr "Группа пользователей \"%(usergroup)s\" уже существует" -#: kallithea/model/validators.py:155 +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" @@ -2106,107 +2101,107 @@ "имя группы пользователей может содержать только буквы, цифры, символы " "подчеркивания, точки и тире; а так же должно начинаться с буквы или цифры" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "Невозможно использовать эту группу как родителя" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "Группа \"%(group_name)s\" уже существует" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "Репозитарий с именем \"%(group_name)s\" уже существует" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "Недопустимые символы (не ascii) в пароле" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "Неверно задан старый пароль" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "Пароли не совпадают" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 #, fuzzy msgid "Invalid username or password" msgstr "неверный пароль" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "Несовпадение токенов" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, fuzzy, python-format msgid "Repository name %(repo)s is not allowed" msgstr "Имя репозитория %(repo)s запрещено" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "Репозитарий %(repo)s уже существует" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "Репозитарий \"%(repo)s\" уже существует в группе \"%(group)s\"" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "Группа репозиториев \"%(repo)s\" уже существует" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy msgid "Invalid repository URL" msgstr "приватный репозиторий" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "Тип форка будет совпадать с родительским" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "У вас недостаточно прав для создания репозиториев в этой группе" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "недостаточно прав для создания репозитория в корневом каталоге" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "У Вас недостаточно привилегий для создания группы в этом месте" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "Данное имя пользователя или группы пользователей недопустимо" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "Этот путь ошибочен" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 #, fuzzy msgid "This email address is already in use" msgstr "Этот E-mail уже занят" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, fuzzy, python-format msgid "Email address \"%(email)s\" not found" msgstr "\"%(email)s\" не существует." -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" @@ -2214,28 +2209,28 @@ "Для входа по LDAP должно быть указано значение аттрибута CN - это " "эквивалент имени пользователя" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "Пожалуйста, введите существующий IPv4 или IPv6 адре" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" "Значение маски подсети должно быть в пределах от 0 до 32 (%(bits)r - " "неверно)" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" "Ключевое имя может только состоять из букв, символа подчеркивания, тире " "или чисел" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "Файла нет в каталоге" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2362,7 +2357,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "Владелец" @@ -2410,7 +2405,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2420,7 +2415,7 @@ msgstr "Загрузка..." #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "Войти" @@ -2435,7 +2430,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "Имя пользователя" @@ -2443,7 +2438,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "Пароль" @@ -2455,7 +2450,7 @@ msgid "Forgot your password ?" msgstr "Забыли пароль?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "Нет аккаунта?" @@ -2494,8 +2489,6 @@ #: kallithea/templates/password_reset.html:47 #, fuzzy -#| msgid "" "Password reset link will be sent to the email address matching -#| your " "username." msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." @@ -2518,13 +2511,11 @@ #: kallithea/templates/password_reset_confirmation.html:39 #, fuzzy -#| msgid "New password" msgid "New Password" msgstr "Новый пароль" #: kallithea/templates/password_reset_confirmation.html:48 #, fuzzy -#| msgid "Confirm new password" msgid "Confirm New Password" msgstr "Подтвердите новый пароль" @@ -2583,10 +2574,6 @@ msgid "There are no branches yet" msgstr "Ветки ещё не созданы" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "Закрытые ветки" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2818,12 +2805,12 @@ msgid "Never" msgstr "никогда" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "Обновить" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "Отмена" @@ -2846,7 +2833,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "Создать новую gist-запись" @@ -2934,7 +2921,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2954,8 +2942,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2982,13 +2968,12 @@ msgstr "создана" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "Показать только текст" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "Мой Аккаунт" @@ -3178,7 +3163,7 @@ msgstr "Комментарии" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "Pull-запросы" @@ -3196,7 +3181,7 @@ msgstr "Показать уведомление" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "Уведомления" @@ -3407,7 +3392,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3679,6 +3664,11 @@ msgid "Unlock Repository" msgstr "Разблокировать репозиторий" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "Подтвердите блокировку репозитория." @@ -3736,10 +3726,6 @@ msgid "Invalidate Repository Cache" msgstr "Сбросить кэш репозитория" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "Подтвердите сброс кэша." - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -4484,21 +4470,17 @@ msgid "Files" msgstr "Файлы" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "Переключиться на" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "Опции" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "Сравнить форк" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4508,111 +4490,116 @@ msgid "Compare" msgstr "Сравнить" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "Поиск" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "Разблокировать" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "Заблокировать" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "Наблюдать" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "Не наблюдать" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "Не наблюдать" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "Форк" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "Создать Pull запрос" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "Показать pull-запросы для %s" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "Переключиться на" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "Показать последнюю активность" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "Общедоступный журнал" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "Показать публичные записи" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "Gist" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "Все публичные Gist-записи" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "Мои публичные Gist-записи" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "Мои приватные Gist-записи" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "Поиск по репозиториям" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "Мои Pull-запросы" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "Не авторизован" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "Авторизоваться" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "Забыли пароль?" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "Выход" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4716,7 +4703,6 @@ #: kallithea/templates/base/root.html:31 #, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "Комментарий в pull-запросе" @@ -4726,7 +4712,6 @@ #: kallithea/templates/base/root.html:33 #, fuzzy -#| msgid "Show Selected Changesets __S → __E" msgid "Show Selected Changesets {0} → {1}" msgstr "Показать выбранные наборы изменений: __S → __E" @@ -4736,6 +4721,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "Свернуть сравнение" @@ -4845,53 +4831,55 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, fuzzy, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" "Статус набора изенений: %s⏎\n" "Кликрните, чтобы перейти к соответствующему pull-request'у #%s" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +msgid "Changeset status: %s by %s" msgstr "Статус набора изменений: %s" -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "Комментарии отсутствуют" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "Закладка %s" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "Метка %s" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "Ветка %s" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "Изменений ещё нет" @@ -4907,7 +4895,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "Добавлено" @@ -4937,22 +4925,22 @@ msgid "Refs" msgstr "Ссылки" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "Добавить или загрузить файлы через Kallithea" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy msgid "Push new repository" msgstr "Отправить новый репозиторий" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "Существующий репозиторий?" @@ -4970,13 +4958,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "Статут изменений" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "Отобразить в формате diff" @@ -4986,7 +4974,7 @@ msgstr "Применить разностное исправление (Patch diff)" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "Скачать diff" @@ -5016,8 +5004,8 @@ msgstr "Создано" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -5026,8 +5014,8 @@ msgstr[2] "%s файла изменено" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -5037,13 +5025,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "Показать полный diff" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy msgid "No revisions" msgstr "версии" @@ -5062,65 +5050,71 @@ msgid "on this changeset" msgstr "Нет изменений" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 msgid "Delete comment?" msgstr "Удалить комментарий?" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "Последние изменения" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +#, fuzzy +msgid "Commenting on line." msgstr "Комментарий к строке {1}." #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" -"Парсинг комментариев выполнен с использованием синтаксиса %s с поддержкой" -" %s." - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" +#, fuzzy +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." msgstr "" "Используйте @имя_пользователя в тексте, чтобы отправить оповещение " "указанному пользователю." -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "Предварительный просмотр комментария" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "Изменить статус ревизии" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +msgid "No change" +msgstr "Без изменений" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Комментарий в pull-запросе" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "Закрыть" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "Применение..." -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "Комментировать" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "Предпросмотр" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "Вам необходимо авторизоваться, чтобы оставлять комментарии." -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "Авторизоваться сейчас" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "Скрыть" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" @@ -5128,7 +5122,7 @@ msgstr[1] "%d комментария" msgstr[2] "%d комментариев" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, python-format msgid "%d inline" msgid_plural "%d inline" @@ -5136,7 +5130,7 @@ msgstr[1] "%d к строкам" msgstr[2] "%d к строкам" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" @@ -5144,29 +5138,6 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -#, fuzzy -msgid "Use @username inside this text to notify another user." -msgstr "" -"Используйте @имя_пользователя в тексте, чтобы отправить оповещение " -"указанному пользователю." - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -msgid "Set changeset status" -msgstr "Изменить статус ревизии" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -msgid "No change" -msgstr "Без изменений" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -msgid "Close" -msgstr "Закрыть" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5176,29 +5147,28 @@ msgid "Files affected" msgstr "Затронутые файлы" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "Удалён" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "Переименован" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "Показать полный diff для этого файла" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "Показать полный diff для этого файла" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "Показать комментарии в строках" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "Удалён" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "Переименован" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "Нет изменений" @@ -5207,6 +5177,11 @@ msgid "Ancestor" msgstr "Предок" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "Статус набора изменений: %s" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5219,29 +5194,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "Показать merge diff" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "Общий предок" -#: kallithea/templates/compare/compare_cs.html:90 +#: kallithea/templates/compare/compare_cs.html:99 msgid "No common ancestor found - repositories are unrelated" msgstr "" -#: kallithea/templates/compare/compare_cs.html:98 +#: kallithea/templates/compare/compare_cs.html:107 msgid "is" msgstr "отстаёт на" -#: kallithea/templates/compare/compare_cs.html:99 +#: kallithea/templates/compare/compare_cs.html:108 #, python-format msgid "%s changesets" msgstr "%s изменений" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "от" @@ -5252,20 +5227,20 @@ msgstr "%s Сравнить" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" @@ -5273,8 +5248,8 @@ msgstr[1] "Показать %s commit'а" msgstr[2] "Показать %s commit'ов" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "Показать полный diff" @@ -5331,21 +5306,26 @@ #: kallithea/templates/email_templates/password_reset.html:6 #, fuzzy -#| msgid "We received a request to create a new password for your account." msgid "We have received a request to reset the password for your account." msgstr "Мы отправили запрос на создание нового пароля для вашего аккаунта." -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5428,8 +5408,9 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "Режим нового файла" +#, fuzzy +msgid "New file type" +msgstr "новый файл" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5560,9 +5541,17 @@ msgid "Binary file (%s)" msgstr "Бинарный файл (%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" -msgstr "Файл слишком большой для отображения" +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "Файл слишком большой для отображения." + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "Показать полный diff." + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." +msgstr "Показать только текст." #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" @@ -5761,9 +5750,9 @@ msgstr "%s Pull-запрос #%s" #: kallithea/templates/pullrequests/pullrequest_show.html:10 -#, fuzzy, python-format +#, python-format msgid "Pull request %s from %s#%s" -msgstr "Pull-запросы №%s от %s#%s" +msgstr "Pull-запросы %s от %s#%s" #: kallithea/templates/pullrequests/pullrequest_show.html:57 #, fuzzy @@ -5831,40 +5820,46 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "Рецензенты pull-запросов" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 msgid "Remove reviewer" msgstr "Удалить рецензента" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -msgid "Potential Reviewers" -msgstr "Потенциальные рецензенты" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +msgid "Potential Reviewers" +msgstr "Потенциальные рецензенты" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "Сохранить изменения" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 #, fuzzy -msgid "Save as New Pull Request" +msgid "Save Updates as New Pull Request" msgstr "Создать новый pull запрос" -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:297 msgid "Cancel Changes" msgstr "Отменить изменения" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 #, fuzzy msgid "Pull Request Content" msgstr "Статус pull-request'а был изменен" @@ -5875,8 +5870,8 @@ msgstr "%s Запросы на внесение изменений" #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 -#, python-format -msgid "Pull Requests from %s'" +#, fuzzy, python-format +msgid "Pull Requests from '%s'" msgstr "Pull-запросы от %s" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 @@ -6083,9 +6078,8 @@ msgstr "Отметьте для скачивания архива с дочерними репозиториями" #: kallithea/templates/summary/summary.html:125 -#, fuzzy msgid "With subrepos" -msgstr "с дочерними репозиториями" +msgstr "С дочерними репозиториями" #: kallithea/templates/summary/summary.html:156 msgid "Repository Size" @@ -6360,27 +6354,6 @@ #~ msgid "reviewer" #~ msgstr "рецензент" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "Сброс пароля произведён, новый пароль был отправлен на ваш email" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "Ваш новый пароль" @@ -6398,8 +6371,6 @@ #~ msgid "Please ignore this email if you did not request a new password ." #~ msgstr "" -#~ "Пожалуйста, проигнорируйте данное сообщение, " -#~ "если вы не запрашивали новый пароль." #~ msgid "Created by" #~ msgstr "Создано" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/sk/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/sk/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/sk/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -7,24 +7,23 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2015-04-01 12:59+0200\n" "Last-Translator: Andrew Shadura \n" "Language-Team: Slovak " "\n" -"Language: sk\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Weblate 2.3-dev\n" - -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +"Generated-By: Babel 1.3\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "Zatiaľ nie sú žiadne zmeny" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -36,35 +35,29 @@ msgid "None" msgstr "" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(zatvorené)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "Ukázať medzery" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "Ignorovať medzery" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" msgstr "" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "Taká revízia neexistuje" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "" @@ -117,10 +110,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "" @@ -129,111 +122,111 @@ msgid "%s committed on %s" msgstr "" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "Kliknite pre pridanie nového súboru" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "Zatiaľ nie sú žiadne súbory. %s" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "Tento repozitár bol uzamknutý používateľom %s dňa %s" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "Zmazaný súbor %s cez Kallithea" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "Úspešne zmazaný súbor %s" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "Došlo k chybe pri ukladaní" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "Žiadne zmeny" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "Pridaný súbor cez Kallithea" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "Žiadny obsah" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "Sťahovanie vypnuté" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "Neznáma revízia %s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "Prázdny repozitár" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "Zmeny" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "Vetvy" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "Tagy" @@ -247,7 +240,7 @@ msgid "Groups" msgstr "Skupiny" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -255,23 +248,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "Repozitáre" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "Vetva" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "Záložka" @@ -282,159 +279,164 @@ msgstr "" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 #, fuzzy msgid "Bad captcha" msgstr "zlá captcha" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "" - -#: kallithea/controllers/login.py:202 +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" msgstr "" -#: kallithea/controllers/login.py:251 +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" msgstr "" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "Úspešne aktualizované heslo" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "%s (zatvorené)" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" msgstr "" -#: kallithea/controllers/pullrequests.py:174 +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" msgstr "" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "Záložky" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" msgstr "" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 -msgid "No permission to change pull request status" -msgstr "" - #: kallithea/controllers/pullrequests.py:727 +msgid "No permission to change pull request status" +msgstr "" + +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "Úspešne zmazaný súbor %s" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." msgstr "" @@ -450,12 +452,12 @@ msgid "An error occurred during search operation." msgstr "Došlo k chybe počas vyhľadávania." -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "" @@ -476,64 +478,64 @@ msgid "Error occurred during update of defaults" msgstr "" +#: kallithea/controllers/admin/gists.py:58 +#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "" + #: kallithea/controllers/admin/gists.py:59 -#: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/my_account.py:244 #: kallithea/controllers/admin/users.py:285 -msgid "Forever" -msgstr "" +msgid "5 minutes" +msgstr "5 minút" #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" -msgstr "5 minút" +msgid "1 hour" +msgstr "1 hodina" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "1 hodina" +msgid "1 day" +msgstr "1 deň" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "1 deň" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "1 mesiac" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "Došlo k chybe pri vytváraní gist" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" msgstr "" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "Došlo k chybe pri aktualizácii gist %s" @@ -548,7 +550,7 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "" @@ -558,33 +560,33 @@ msgstr "Došlo k chybe pri aktualizácii hesla užívateľa" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "Došlo k chybe pri ukladaní e-mailovej adresy" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -634,10 +636,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "" @@ -668,7 +670,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "" @@ -680,7 +682,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "" @@ -701,244 +703,244 @@ msgid "Error occurred during update of permissions" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "Nič" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "Tento repozitár bol uzamknutý používateľom %s dňa %s" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "Tento repozitár bol uzamknutý používateľom %s dňa %s" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "" @@ -979,76 +981,80 @@ msgstr "" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "" @@ -1064,125 +1070,125 @@ msgid "No changes detected" msgstr "" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "" -#: kallithea/lib/helpers.py:696 -msgid "and" -msgstr "" - #: kallithea/lib/helpers.py:697 +msgid "and" +msgstr "" + +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, python-format msgid "Fork name %s" msgstr "" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, python-format msgid "Pull request %s" msgstr "" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "" @@ -1192,8 +1198,8 @@ msgstr "" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "" @@ -1217,7 +1223,7 @@ msgid "chmod" msgstr "" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1225,7 +1231,7 @@ "repositories" msgstr "" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" @@ -1233,7 +1239,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" @@ -1241,7 +1247,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" @@ -1249,7 +1255,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" @@ -1257,7 +1263,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" @@ -1265,7 +1271,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" @@ -1273,27 +1279,27 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "" @@ -1392,7 +1398,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "" @@ -1503,7 +1509,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "" @@ -1518,7 +1524,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "" @@ -1545,7 +1551,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "" @@ -1692,7 +1698,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "" @@ -1719,12 +1725,12 @@ msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1733,106 +1739,106 @@ msgid "on line %s" msgstr "" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 -msgid "Default user has read access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1669 -msgid "Default user has write access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" +msgid "Default user has read access to new repositories" msgstr "" #: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" +msgid "Default user has write access to new repositories" msgstr "" #: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" msgstr "" #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -msgid "Only admins can create repository groups" +msgid "Default user has read access to new user groups" msgstr "" #: kallithea/model/db.py:1683 -msgid "Non-admins can create repository groups" -msgstr "" - -#: kallithea/model/db.py:1685 -msgid "Only admins can create user groups" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" msgstr "" #: kallithea/model/db.py:1686 -msgid "Non-admins can create user groups" -msgstr "" - -#: kallithea/model/db.py:1688 -msgid "Only admins can create top level repositories" +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" msgstr "" #: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 #, fuzzy msgid "Only admins can fork repositories" msgstr "Repozitáre" -#: kallithea/model/db.py:1695 +#: kallithea/model/db.py:1699 #, fuzzy -msgid "Non-admins can can fork repositories" +msgid "Non-admins can fork repositories" msgstr "Repozitáre" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 msgid "User registration with manual account activation" msgstr "" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "" @@ -1855,7 +1861,7 @@ msgid "Enter %(min)i characters or more" msgstr "" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1948,7 +1954,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "" @@ -1981,15 +1987,15 @@ "owners or remove those user groups: %s" msgstr "" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -2000,168 +2006,168 @@ msgid "Value cannot be an empty list" msgstr "" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "" -#: kallithea/model/validators.py:153 -#, python-format -msgid "User group \"%(usergroup)s\" already exists" -msgstr "" - #: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" msgstr "" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 msgid "Invalid username or password" msgstr "" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy msgid "Invalid repository URL" msgstr "Odblokovať repozitár" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 msgid "This email address is already in use" msgstr "" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2288,7 +2294,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "" @@ -2336,7 +2342,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2346,7 +2352,7 @@ msgstr "" #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "" @@ -2361,7 +2367,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "" @@ -2369,7 +2375,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "" @@ -2381,7 +2387,7 @@ msgid "Forgot your password ?" msgstr "" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "" @@ -2502,10 +2508,6 @@ msgid "There are no branches yet" msgstr "" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2734,12 +2736,12 @@ msgid "Never" msgstr "" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "" @@ -2762,7 +2764,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2850,7 +2852,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2870,8 +2873,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2898,13 +2899,12 @@ msgstr "" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "" @@ -3087,7 +3087,7 @@ msgstr "" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "" @@ -3105,7 +3105,7 @@ msgstr "" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "" @@ -3304,7 +3304,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3569,6 +3569,11 @@ msgid "Unlock Repository" msgstr "Odblokovať repozitár" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 msgid "Confirm to lock repository." msgstr "Potvrdenie blokovania repozitára." @@ -3626,10 +3631,6 @@ msgid "Invalidate Repository Cache" msgstr "" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -msgid "Confirm to invalidate repository cache." -msgstr "" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -4368,21 +4369,17 @@ msgid "Files" msgstr "" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4392,111 +4389,116 @@ msgid "Compare" msgstr "" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4599,7 +4601,6 @@ #: kallithea/templates/base/root.html:31 #, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "Zmena stavu" @@ -4617,6 +4618,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 msgid "Collapse Diff" msgstr "" @@ -4727,51 +4729,54 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Set changeset status" +msgid "Changeset status: %s by %s" +msgstr "Zmeny" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "" @@ -4787,7 +4792,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4817,22 +4822,22 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy msgid "Push new repository" msgstr "Prázdny repozitár" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "" @@ -4850,13 +4855,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -4866,7 +4871,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -4893,8 +4898,8 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" @@ -4903,8 +4908,8 @@ msgstr[2] "" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4914,13 +4919,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy msgid "No revisions" msgstr "Neznáma revízia %s" @@ -4939,62 +4944,71 @@ msgid "on this changeset" msgstr "%s zmien" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 #, fuzzy msgid "Delete comment?" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "Posledné zmeny" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +msgid "Commenting on line." msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +#, fuzzy +msgid "Set changeset status" +msgstr "Zmeny" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +#, fuzzy +msgid "No change" +msgstr "Žiadne zmeny" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "Zmena stavu" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +#, fuzzy +msgid "Close" +msgstr "(zatvorené)" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" @@ -5002,7 +5016,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, fuzzy, python-format msgid "%d inline" msgid_plural "%d inline" @@ -5010,7 +5024,7 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" @@ -5018,29 +5032,6 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -#, fuzzy -msgid "Set changeset status" -msgstr "Zmeny" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -#, fuzzy -msgid "No change" -msgstr "Žiadne zmeny" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -#, fuzzy -msgid "Close" -msgstr "(zatvorené)" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5050,29 +5041,28 @@ msgid "Files affected" msgstr "" -#: kallithea/templates/changeset/diff_block.html:21 +#: kallithea/templates/changeset/diff_block.html:54 +msgid "Deleted" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:57 +msgid "Renamed" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:66 #: kallithea/templates/files/diff_2way.html:43 msgid "Show full diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 +#: kallithea/templates/changeset/diff_block.html:69 #: kallithea/templates/files/diff_2way.html:46 msgid "Show full side-by-side diff for this file" msgstr "" -#: kallithea/templates/changeset/diff_block.html:38 +#: kallithea/templates/changeset/diff_block.html:83 msgid "Show inline comments" msgstr "" -#: kallithea/templates/changeset/diff_block.html:86 -msgid "Deleted" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:89 -msgid "Renamed" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "" @@ -5081,6 +5071,11 @@ msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5093,29 +5088,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 #, fuzzy, python-format msgid "%s changesets" msgstr "%s zmien" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 msgid "behind" msgstr "" @@ -5126,20 +5121,20 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" @@ -5147,8 +5142,8 @@ msgstr[1] "" msgstr[2] "" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5207,17 +5202,23 @@ msgid "We have received a request to reset the password for your account." msgstr "" -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5300,7 +5301,7 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" +msgid "New file type" msgstr "" #: kallithea/templates/files/files_add.html:64 @@ -5433,8 +5434,16 @@ msgid "Binary file (%s)" msgstr "" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +msgid "File is too big to display." +msgstr "" + +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." msgstr "" #: kallithea/templates/files/files_ypjax.html:5 @@ -5699,41 +5708,47 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 #, fuzzy msgid "Remove reviewer" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -#, fuzzy -msgid "Potential Reviewers" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +#, fuzzy +msgid "Potential Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 msgid "Cancel Changes" msgstr "Zrušiť zmeny" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 msgid "Pull Request Content" msgstr "" @@ -5743,9 +5758,9 @@ msgstr "" #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 -#, python-format -msgid "Pull Requests from %s'" -msgstr "" +#, fuzzy, python-format +msgid "Pull Requests from '%s'" +msgstr "Zmena stavu" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 #, python-format @@ -6314,39 +6329,12 @@ #~ msgid "Your password reset link was sent" #~ msgstr "" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "" #~ msgid "Your new Kallithea password:%s" #~ msgstr "" -#~ msgid "" -#~ "Password reset link will be sent " -#~ "to the email address matching your " -#~ "username." -#~ msgstr "" - #~ msgid "Open New Pull Request for Selected Changesets" #~ msgstr "" @@ -6368,3 +6356,50 @@ #~ msgid "Created by" #~ msgstr "" +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "You can only edit files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "" + +#~ msgid "Commenting on line {1}." +#~ msgstr "" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "" + +#~ msgid "Preview" +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user." +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "File is too big to display" +#~ msgstr "" + +#~ msgid "Save as New Pull Request" +#~ msgstr "" + +#~ msgid "Pull Requests from %s'" +#~ msgstr "" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -9,23 +9,25 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" -"PO-Revision-Date: 2014-12-12 14:19+0200\n" -"Last-Translator: Michal Čihař \n" -"Language-Team: Simplified Chinese " +"POT-Creation-Date: 2016-03-14 16:51+0100\n" +"PO-Revision-Date: 2016-03-24 15:03+0000\n" +"Last-Translator: YFdyh000 \n" +"Language-Team: Chinese (China) " "\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" - -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +"X-Generator: Weblate 2.6-dev\n" +"Generated-By: Babel 1.3\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "还没有修订集" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -37,51 +39,44 @@ msgid "None" msgstr "无" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" -msgstr "" - -#: kallithea/controllers/changeset.py:89 +msgstr "(已关闭)" + +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" -msgstr "" - -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +msgstr "显示空白" + +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" -msgstr "" - -#: kallithea/controllers/changeset.py:169 +msgstr "忽略空白" + +#: kallithea/controllers/changeset.py:168 #, python-format msgid "Increase diff context to %(num)s lines" -msgstr "" - -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +msgstr "增加差异上下文到 %(num)s 行" + +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" -msgstr "" +msgstr "选择修订集" #: kallithea/controllers/compare.py:261 msgid "Cannot compare repositories without using common ancestor" msgstr "" #: kallithea/controllers/error.py:71 -#, fuzzy msgid "No response" -msgstr "修订" +msgstr "无响应" #: kallithea/controllers/error.py:72 msgid "Unknown error" -msgstr "" +msgstr "未知错误" #: kallithea/controllers/error.py:100 msgid "The request could not be understood by the server due to malformed syntax." @@ -118,123 +113,123 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." -msgstr "修订集太大已被截断......" +msgstr "修订集太大并已被截断..." #: kallithea/controllers/feed.py:91 #, python-format msgid "%s committed on %s" msgstr "" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" -msgstr "" - -#: kallithea/controllers/files.py:92 +msgstr "点击这里添加新文件" + +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" -msgstr "" - -#: kallithea/controllers/files.py:193 -#, fuzzy, python-format +msgstr "这里还没有文件。%s" + +#: kallithea/controllers/files.py:195 +#, python-format msgid "%s at %s" -msgstr "%s零%s" - -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +msgstr "%s 在 %s" + +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "版本库由%s于%s锁定" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" +msgstr "您只能删除有效分支的修订中的文件" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" -msgstr "" - -#: kallithea/controllers/files.py:350 +msgstr "删除文件 %s 通过 Kallithea" + +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" -msgstr "" - -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +msgstr "成功删除文件 %s" + +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "提交时发生错误" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" +msgstr "您只能编辑有效分支的修订中的文件" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" -msgstr "" - -#: kallithea/controllers/files.py:407 +msgstr "已编辑文件 %s 通过 Kallithea" + +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "无变更" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "成功提交到%s" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" -msgstr "" - -#: kallithea/controllers/files.py:464 +msgstr "已添加文件通过 Kallithea" + +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "无内容" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "无文件名" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" -msgstr "" - -#: kallithea/controllers/files.py:537 +msgstr "下载已禁用" + +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "未知版本%s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "空版本库" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "未知包类型" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "修订集" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "分支" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "标签" @@ -246,9 +241,9 @@ #: kallithea/controllers/home.py:84 msgid "Groups" -msgstr "" - -#: kallithea/controllers/home.py:89 +msgstr "组" + +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -256,25 +251,29 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "版本库" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" -msgstr "" - -#: kallithea/controllers/home.py:136 +msgstr "分支" + +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "已关闭分支" + +#: kallithea/controllers/home.py:151 msgid "Tag" -msgstr "" - -#: kallithea/controllers/home.py:142 +msgstr "标签" + +#: kallithea/controllers/home.py:157 msgid "Bookmark" -msgstr "" +msgstr "书签" #: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153 #: kallithea/templates/journal/public_journal.html:4 @@ -283,169 +282,165 @@ msgstr "公共日志" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "日志" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" -msgstr "" - -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "" - -#: kallithea/controllers/login.py:202 -#, fuzzy -#| msgid "Your password reset link was sent" +msgstr "验证码错误" + +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "您已成功注册 %s" + +#: kallithea/controllers/login.py:195 msgid "A password reset confirmation code has been sent" -msgstr "密码重置链接已经发送" - -#: kallithea/controllers/login.py:251 -#, fuzzy -#| msgid "Your password reset link was sent" +msgstr "密码重置确认码已经发送" + +#: kallithea/controllers/login.py:244 msgid "Invalid password reset token" -msgstr "密码重置链接已经发送" - -#: kallithea/controllers/login.py:256 +msgstr "无效的密码重置令牌" + +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" -msgstr "" - -#: kallithea/controllers/pullrequests.py:124 +msgstr "成功更新密码" + +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" -msgstr "" - -#: kallithea/controllers/pullrequests.py:152 +msgstr "%s (已关闭)" + +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "修订集" -#: kallithea/controllers/pullrequests.py:173 +#: kallithea/controllers/pullrequests.py:172 msgid "Special" -msgstr "" - -#: kallithea/controllers/pullrequests.py:174 +msgstr "特殊" + +#: kallithea/controllers/pullrequests.py:173 msgid "Peer branches" -msgstr "" - -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +msgstr "同等分支" + +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "书签" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" -msgstr "" - -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 -#, fuzzy +msgstr "创建拉取请求出错:%s" + +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 msgid "No description" -msgstr "描述" - -#: kallithea/controllers/pullrequests.py:363 +msgstr "无描述" + +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "成功提交拉取请求" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" -msgstr "" - -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 -#, fuzzy +msgstr "指定的审核者 \"%s\" 无效" + +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" -msgstr "提交拉取请求时发生错误" - -#: kallithea/controllers/pullrequests.py:401 +msgstr "创建拉取请求时发生错误" + +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" -msgstr "" - -#: kallithea/controllers/pullrequests.py:408 +msgstr "缺少上次拉取请求之后的修订集:" + +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" -msgstr "" - -#: kallithea/controllers/pullrequests.py:415 +msgstr "在上次拉取请求之后,在 %s %s 上的新修订集:" + +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." -msgstr "" - -#: kallithea/controllers/pullrequests.py:470 -#, fuzzy +msgstr "已关闭,被 %s 替换。" + +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" -msgstr "拉取请求检视人员" - -#: kallithea/controllers/pullrequests.py:513 -#, fuzzy +msgstr "拉取请求更新已创建" + +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" -msgstr "拉取请求" - -#: kallithea/controllers/pullrequests.py:528 +msgstr "拉取请求已更新" + +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "成功删除拉取请求" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." -msgstr "" - -#: kallithea/controllers/pullrequests.py:625 +msgstr "没有找到更新此拉取请求的修订集。" + +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 -msgid "No permission to change pull request status" -msgstr "" - #: kallithea/controllers/pullrequests.py:727 -#, fuzzy +msgid "No permission to change pull request status" +msgstr "" + +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "成功删除拉取请求" + +#: kallithea/controllers/pullrequests.py:748 msgid "Closing." -msgstr "使用中" +msgstr "关闭。" #: kallithea/controllers/search.py:135 msgid "Invalid search query. Try quoting it." @@ -456,28 +451,26 @@ msgstr "没有索引用于搜索。请运行whoosh索引器" #: kallithea/controllers/search.py:144 -#, fuzzy msgid "An error occurred during search operation." -msgstr "在搜索操作中发生异常" - -#: kallithea/controllers/summary.py:180 +msgstr "搜索操作期间发生错误。" + +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 -#, fuzzy msgid "No data ready yet" -msgstr "数据未加载" - -#: kallithea/controllers/summary.py:183 +msgstr "数据尚未就绪" + +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "该版本库统计功能已经禁用" #: kallithea/controllers/admin/auth_settings.py:135 msgid "Auth settings updated successfully" -msgstr "" +msgstr "验证设置更新成功" #: kallithea/controllers/admin/auth_settings.py:146 msgid "error occurred during update of auth settings" -msgstr "" +msgstr "验证设置更新时发生错误" #: kallithea/controllers/admin/defaults.py:97 msgid "Default settings updated successfully" @@ -485,71 +478,70 @@ #: kallithea/controllers/admin/defaults.py:112 msgid "Error occurred during update of defaults" -msgstr "" - -#: kallithea/controllers/admin/gists.py:59 +msgstr "默认值更新时发生错误" + +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 -#: kallithea/controllers/admin/users.py:285 +#: kallithea/controllers/admin/users.py:284 #, fuzzy msgid "Forever" msgstr "检视者" +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/users.py:285 +msgid "5 minutes" +msgstr "5 分钟" + #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" -msgstr "" +msgid "1 hour" +msgstr "1 小时" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" -msgstr "" +msgid "1 day" +msgstr "1 天" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" -msgstr "" - -#: kallithea/controllers/admin/gists.py:67 +msgstr "1 个月" + +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" -msgstr "" - -#: kallithea/controllers/admin/gists.py:146 +msgstr "终身" + +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" -msgstr "" - -#: kallithea/controllers/admin/gists.py:184 +msgstr "gist 创建时发生错误" + +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" -msgstr "" - -#: kallithea/controllers/admin/gists.py:233 -#, fuzzy +msgstr "已删除 gist %s" + +#: kallithea/controllers/admin/gists.py:232 msgid "Unmodified" -msgstr "最后修改于" - -#: kallithea/controllers/admin/gists.py:262 +msgstr "未修改" + +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" -msgstr "" - -#: kallithea/controllers/admin/gists.py:267 +msgstr "成功更新 gist 内容" + +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" -msgstr "" - -#: kallithea/controllers/admin/gists.py:270 +msgstr "成功更新 gist 数据" + +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" -msgstr "" +msgstr "gist %s 更新时发生错误" #: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215 #: kallithea/model/user.py:237 @@ -561,45 +553,45 @@ msgstr "你的帐号已经更新完成" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" -msgstr "" +msgstr "用户 %s 更新时发生错误" #: kallithea/controllers/admin/my_account.py:178 msgid "Error occurred during update of user password" -msgstr "" +msgstr "用户密码更新时发生错误" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "已为用户添加电子邮件 %s" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "保存电子邮件时发生错误" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "成功删除用户电子邮件" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" -msgstr "" +msgstr "API 密钥创建成功" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" -msgstr "" +msgstr "API 密钥重置成功" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" -msgstr "" +msgstr "API 密钥删除成功" #: kallithea/controllers/admin/permissions.py:62 #: kallithea/controllers/admin/permissions.py:66 @@ -647,10 +639,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "管理" @@ -667,11 +659,11 @@ #: kallithea/controllers/admin/permissions.py:77 msgid "Allowed with manual account activation" -msgstr "" +msgstr "已允许手动激活账号" #: kallithea/controllers/admin/permissions.py:79 msgid "Allowed with automatic account activation" -msgstr "" +msgstr "已允许自动激活账号" #: kallithea/controllers/admin/permissions.py:82 #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439 @@ -681,9 +673,9 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" -msgstr "" +msgstr "外部账号手动激活" #: kallithea/controllers/admin/permissions.py:83 #: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440 @@ -693,9 +685,9 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" -msgstr "" +msgstr "外部账号自动激活" #: kallithea/controllers/admin/permissions.py:87 #: kallithea/controllers/admin/permissions.py:90 @@ -708,250 +700,250 @@ #: kallithea/controllers/admin/permissions.py:124 msgid "Global permissions updated successfully" -msgstr "" +msgstr "全局权限更新成功" #: kallithea/controllers/admin/permissions.py:139 msgid "Error occurred during update of permissions" -msgstr "" - -#: kallithea/controllers/admin/repo_groups.py:188 +msgstr "权限更新时发生错误" + +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "这个组内有%s个版本库因而无法删除" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "版本库%s成功更新" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, fuzzy, python-format msgid "Cannot delete repository %s which still has forks" msgstr "无法删除%s因为它还有其他分复刻本库" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "在删除%s的时候发生错误" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "成功更新在公共日志中的可见性" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "设置版本库到公共日志时发生错误" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "无" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "成功将版本库%s标记为复刻自%s" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "在搜索操作中发生错误" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "版本库未锁定" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "版本库未锁定" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "解锁时发生错误" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "清除缓存时发生错误" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "成功拉取自远程路径" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "从远程路径拉取时发生错误" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "删除版本库统计时发生错误" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "成功更新版本控制系统设置" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "更新应用设置" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "成功更新可视化设置" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "新建钩子" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "更新钩子" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "Whoosh重新索引任务调度" @@ -992,76 +984,80 @@ msgstr "" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "保存权限时发生错误" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "用户更新成功" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "删除用户时发生错误" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "必须是注册用户才能进行此操作" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "必须登录才能访问该页面" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "未找到修订集" @@ -1071,132 +1067,132 @@ #: kallithea/lib/diffs.py:82 msgid "Changeset was too big and was cut off, use diff menu to display this diff" -msgstr "修订集因过大而被截断,可查看原始修订集作为替代" +msgstr "修订集过大并已被截断,使用差异菜单查看此差异" #: kallithea/lib/diffs.py:92 msgid "No changes detected" msgstr "未发现差异" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "已经删除分支%s" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "创建标签%s" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" -msgstr "显示合并的修订集%s->%s" - -#: kallithea/lib/helpers.py:677 +msgstr "显示所有合并的修订集 %s->%s" + +#: kallithea/lib/helpers.py:678 #, fuzzy msgid "Compare view" msgstr "比较显示" -#: kallithea/lib/helpers.py:696 +#: kallithea/lib/helpers.py:697 msgid "and" msgstr "还有" -#: kallithea/lib/helpers.py:697 +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "%s个" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "修订" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, fuzzy, python-format msgid "Fork name %s" msgstr "复刻名称%s" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, fuzzy, python-format msgid "Pull request %s" msgstr "拉取请求#%s" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "[删除]版本库" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "[创建]版本库" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "[创建]复刻版本库" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "[复刻]版本库" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "[更新]版本库" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "[删除]版本库" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "[创建]用户" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "[更新]用户" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "[评论]了版本库中的修订" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "[评论]拉取请求" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "[关闭] 拉取请求" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "[推送]到" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "[通过Kallithea提交]到版本库" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "[远程拉取]到版本库" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "[拉取]自" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "[开始关注]版本库" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "[停止关注]版本库" @@ -1206,8 +1202,8 @@ msgstr " 还有%s个" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "无文件" @@ -1231,7 +1227,7 @@ msgid "chmod" msgstr "" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1239,63 +1235,63 @@ "repositories" msgstr "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启Kallithea以重新扫描版本库" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "%d年" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "%d月" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "%d天" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d时" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d分" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "%d秒" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "%s" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "%s前" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "%s零%s" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "%s零%s前" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "刚才" @@ -1394,7 +1390,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "Kallithea 管理员" @@ -1505,7 +1501,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "已批准" @@ -1520,7 +1516,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "驳回" @@ -1547,7 +1543,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "" @@ -1694,7 +1690,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "" @@ -1721,12 +1717,12 @@ msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1735,110 +1731,110 @@ msgid "on line %s" msgstr "在%s行" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "[提及]" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 +#: kallithea/model/db.py:1672 #, fuzzy msgid "Default user has read access to new repositories" msgstr "未授权的资源访问" -#: kallithea/model/db.py:1669 +#: kallithea/model/db.py:1673 #, fuzzy msgid "Default user has write access to new repositories" msgstr "未授权的资源访问" -#: kallithea/model/db.py:1670 +#: kallithea/model/db.py:1674 msgid "Default user has admin access to new repositories" msgstr "" -#: kallithea/model/db.py:1672 +#: kallithea/model/db.py:1676 msgid "Default user has no access to new repository groups" msgstr "" -#: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" -msgstr "" - #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -#, fuzzy -msgid "Only admins can create repository groups" -msgstr "没有在该版本库组中创建版本库的权限" +msgid "Default user has read access to new user groups" +msgstr "" #: kallithea/model/db.py:1683 -#, fuzzy -msgid "Non-admins can create repository groups" -msgstr "没有在该版本库组中创建版本库的权限" - -#: kallithea/model/db.py:1685 -msgid "Only admins can create user groups" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" msgstr "" #: kallithea/model/db.py:1686 -msgid "Non-admins can create user groups" -msgstr "" - -#: kallithea/model/db.py:1688 -msgid "Only admins can create top level repositories" -msgstr "" +#, fuzzy +msgid "Only admins can create repository groups" +msgstr "没有在该版本库组中创建版本库的权限" + +#: kallithea/model/db.py:1687 +#, fuzzy +msgid "Non-admins can create repository groups" +msgstr "没有在该版本库组中创建版本库的权限" #: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 #, fuzzy msgid "Only admins can fork repositories" msgstr "创建版本库" -#: kallithea/model/db.py:1695 +#: kallithea/model/db.py:1699 #, fuzzy -msgid "Non-admins can can fork repositories" +msgid "Non-admins can fork repositories" msgstr "创建版本库" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 msgid "User registration with manual account activation" msgstr "" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "未检视" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "检视中" @@ -1861,14 +1857,14 @@ msgid "Enter %(min)i characters or more" msgstr "输入少于%(min)i个字符" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" #: kallithea/model/notification.py:254 #, python-format msgid "%(user)s commented on changeset %(age)s" -msgstr "" +msgstr "%(user)s 已评论修订集在 %(age)s" #: kallithea/model/notification.py:255 #, python-format @@ -1898,7 +1894,7 @@ #: kallithea/model/notification.py:266 #, python-format msgid "%(user)s commented on changeset at %(when)s" -msgstr "" +msgstr "%(user)s 已评论修订集于 %(when)s" #: kallithea/model/notification.py:267 #, python-format @@ -1928,7 +1924,7 @@ #: kallithea/model/notification.py:302 #, python-format msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s" -msgstr "" +msgstr "[评论] %(repo_name)s 修订集 %(short_id)s 在 %(branch)s" #: kallithea/model/notification.py:305 #, fuzzy, python-format @@ -1942,7 +1938,6 @@ #: kallithea/model/notification.py:308 #, fuzzy, python-format -#| msgid "[commented] on pull request for" msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s" msgstr "[评论]拉取请求" @@ -1956,7 +1951,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "最新tip版本" @@ -1990,17 +1985,16 @@ "owners or remove those user groups: %s" msgstr "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 #, fuzzy -#| msgid "Password confirmation" msgid "Password reset notification" msgstr "确认密码" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -2011,170 +2005,170 @@ msgid "Value cannot be an empty list" msgstr "值不能为空" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "用户名称%(username)s已经存在" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, fuzzy, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "用户名称 %(username)s 无效" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "用户名称 %(username)s 无效" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "" -#: kallithea/model/validators.py:153 -#, python-format -msgid "User group \"%(usergroup)s\" already exists" -msgstr "" - #: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" msgstr "" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "不能将这个组作为parent" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "组 \"%(group_name)s\" 已经存在" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "已经存在名为 \"%(group_name)s\" 的版本库" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "密码含有无效(非ASCII)字符" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "密码不符" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 #, fuzzy msgid "Invalid username or password" msgstr "无效密码" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "令牌不匹配" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, fuzzy, python-format msgid "Repository name %(repo)s is not allowed" msgstr "版本库名称不能为%(repo)s" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "已经存在版本库%(repo)s" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "版本库组 \"%(group)s\" 中已经存在版本库 \"%(repo)s\"" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy msgid "Invalid repository URL" msgstr "私有版本库" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "复刻版本库必须和父版本库类型相同" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "没有在该版本库组中创建版本库的权限" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "不是一个合法的路径" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 #, fuzzy msgid "This email address is already in use" msgstr "该邮件地址已被使用" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, fuzzy, python-format msgid "Email address \"%(email)s\" not found" msgstr "邮件地址\"%(email)s\"不存在" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "LDAP 登陆属性的 CN 必须指定 - 这个名字作为用户名" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2301,7 +2295,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "所有者" @@ -2344,12 +2338,12 @@ #: kallithea/templates/journal/journal.html:292 #: kallithea/templates/tags/tags.html:82 msgid "Data error." -msgstr "数据错误" +msgstr "数据错误。" #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2359,7 +2353,7 @@ msgstr "载入中..." #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "登录" @@ -2374,7 +2368,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "帐号" @@ -2382,7 +2376,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "密码" @@ -2394,7 +2388,7 @@ msgid "Forgot your password ?" msgstr "忘记了密码?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "还没有帐号?" @@ -2435,8 +2429,6 @@ #: kallithea/templates/password_reset.html:47 #, fuzzy -#| msgid "" "Password reset link will be sent to the email address matching -#| your " "username." msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." @@ -2459,13 +2451,11 @@ #: kallithea/templates/password_reset_confirmation.html:39 #, fuzzy -#| msgid "New password" msgid "New Password" msgstr "新密码" #: kallithea/templates/password_reset_confirmation.html:48 #, fuzzy -#| msgid "New password" msgid "Confirm New Password" msgstr "新密码" @@ -2524,10 +2514,6 @@ msgid "There are no branches yet" msgstr "没有任何分支" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2686,7 +2672,7 @@ #: kallithea/templates/admin/defaults/defaults.html:57 #: kallithea/templates/admin/repos/repo_edit_settings.html:88 msgid "Enable statistics window on summary page." -msgstr "启用概况页的统计窗口" +msgstr "启用概况页面上的统计窗口。" #: kallithea/templates/admin/defaults/defaults.html:63 #: kallithea/templates/admin/repos/repo_edit_settings.html:93 @@ -2696,7 +2682,7 @@ #: kallithea/templates/admin/defaults/defaults.html:67 #: kallithea/templates/admin/repos/repo_edit_settings.html:97 msgid "Enable download menu on summary page." -msgstr "启用概况页的下载菜单" +msgstr "启用概况页面上的下载菜单。" #: kallithea/templates/admin/defaults/defaults.html:73 #: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34 @@ -2707,7 +2693,7 @@ #: kallithea/templates/admin/defaults/defaults.html:77 #: kallithea/templates/admin/repos/repo_edit_settings.html:106 msgid "Enable lock-by-pulling on repository." -msgstr "启用版本库的拉取锁定" +msgstr "启用版本库的拉取锁定。" #: kallithea/templates/admin/gists/edit.html:5 #: kallithea/templates/admin/gists/edit.html:18 @@ -2757,12 +2743,12 @@ msgid "Never" msgstr "检视者" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "" @@ -2785,7 +2771,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2873,7 +2859,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2893,8 +2880,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2921,13 +2906,12 @@ msgstr "" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "我的账户" @@ -3065,7 +3049,7 @@ #: kallithea/templates/admin/my_account/my_account_profile.html:12 #: kallithea/templates/admin/users/user_edit_profile.html:9 msgid "Using" -msgstr "使用中" +msgstr "使用" #: kallithea/templates/admin/my_account/my_account_profile.html:14 #: kallithea/templates/admin/users/user_edit_profile.html:11 @@ -3095,7 +3079,7 @@ #: kallithea/templates/journal/journal.html:291 #: kallithea/templates/tags/tags.html:81 msgid "No records found." -msgstr "没有找到记录" +msgstr "没有找到记录。" #: kallithea/templates/admin/my_account/my_account_watched.html:1 #, fuzzy @@ -3116,7 +3100,7 @@ msgstr "评论" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "" @@ -3136,7 +3120,7 @@ msgstr "显示通知" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "通知" @@ -3338,7 +3322,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3498,11 +3482,11 @@ #: kallithea/templates/admin/repos/repo_edit_settings.html:39 #: kallithea/templates/forks/fork.html:52 msgid "Optionally select a group to put this repository into." -msgstr "可选的,选择一个组将版本库放到其中" +msgstr "可选的选择一个组将版本库放到其中。" #: kallithea/templates/admin/repos/repo_add_base.html:59 msgid "Type of repository to create." -msgstr "要创建的版本库类型" +msgstr "要创建的版本库类型。" #: kallithea/templates/admin/repos/repo_add_base.html:64 #: kallithea/templates/admin/repos/repo_edit_settings.html:44 @@ -3614,6 +3598,11 @@ msgid "Unlock Repository" msgstr "公共版本库" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 #, fuzzy msgid "Confirm to lock repository." @@ -3673,11 +3662,6 @@ msgid "Invalidate Repository Cache" msgstr "清除版本库缓存" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -#, fuzzy -msgid "Confirm to invalidate repository cache." -msgstr "确认清除版本库缓存" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 #, fuzzy msgid "" @@ -3812,7 +3796,7 @@ #: kallithea/templates/admin/repos/repo_edit_settings.html:58 msgid "Change owner of this repository." -msgstr "修改这个版本库的所有者" +msgstr "修改这个版本库的所有者。" #: kallithea/templates/admin/repos/repo_edit_statistics.html:6 msgid "Processed commits" @@ -4433,22 +4417,18 @@ msgid "Files" msgstr "浏览" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "选项" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 #, fuzzy msgid "Compare Fork" msgstr "比较复刻" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4458,113 +4438,118 @@ msgid "Compare" msgstr "比较显示" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "搜索" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "复刻" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "公共日志" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 #, fuzzy msgid "My Pull Requests" msgstr "拉取请求" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 #, fuzzy msgid "Login to Your Account" msgstr "登录" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "忘记密码?" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "退出" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4667,7 +4652,6 @@ #: kallithea/templates/base/root.html:31 #, fuzzy -#| msgid "on pull request" msgid "Open New Pull Request from {0}" msgstr "[评论]拉取请求" @@ -4676,10 +4660,8 @@ msgstr "" #: kallithea/templates/base/root.html:33 -#, fuzzy -#| msgid "Show Selected Changeset __S" msgid "Show Selected Changesets {0} → {1}" -msgstr "显示合并的修订集%s->%s" +msgstr "显示选中的修订集 {0} → {1}" #: kallithea/templates/base/root.html:34 #, fuzzy @@ -4688,6 +4670,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 #, fuzzy msgid "Collapse Diff" msgstr "文件差异" @@ -4714,9 +4697,8 @@ msgstr "" #: kallithea/templates/base/root.html:42 -#, fuzzy msgid "Specify changeset" -msgstr "%s修订集" +msgstr "指定修订集" #: kallithea/templates/bookmarks/bookmarks.html:5 #, python-format @@ -4800,51 +4782,56 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" +"修订集状态:%s 由 %s\n" +"点击打开相关联的拉取请求 %s" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, python-format +#| msgid "Changeset status" +msgid "Changeset status: %s by %s" +msgstr "修订集状态:%s 由 %s" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +msgstr "修订集有评论" + +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "没有任何变更" @@ -4860,7 +4847,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4890,29 +4877,29 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "通过Kallithea直接添加或者上传文件" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy msgid "Push new repository" msgstr "推送新版本库" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "已有版本库?" #: kallithea/templates/changeset/changeset.html:8 #, python-format msgid "%s Changeset" -msgstr "%s修订集" +msgstr "%s 修订集" #: kallithea/templates/changeset/changeset.html:36 msgid "Parent rev." @@ -4923,13 +4910,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "修订集状态" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -4939,7 +4926,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -4970,16 +4957,16 @@ msgstr "创建于" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" msgstr[0] "修改%s个文件" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4987,13 +4974,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy msgid "No revisions" msgstr "修订" @@ -5009,186 +4996,174 @@ msgstr "无文件" #: kallithea/templates/changeset/changeset_file_comment.html:24 -#, fuzzy msgid "on this changeset" -msgstr "无修订" - -#: kallithea/templates/changeset/changeset_file_comment.html:30 +msgstr "在此修订集" + +#: kallithea/templates/changeset/changeset_file_comment.html:31 #, fuzzy msgid "Delete comment?" msgstr "%d条评论" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "文件已更改" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +#, fuzzy +msgid "Commenting on line." msgstr "在{1}行上评论" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "评论使用%s语法并支持%s" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 #, fuzzy -msgid "Use @username inside this text to notify another user" +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户" -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +#: kallithea/templates/changeset/changeset_file_comment.html:67 +msgid "Set changeset status" +msgstr "设置修订集状态" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +#, fuzzy +msgid "No change" +msgstr "无变更" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "[评论]拉取请求" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +#, fuzzy +msgid "Close" +msgstr "已关闭" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 #, fuzzy msgid "Submitting ..." msgstr "提交中……" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "评论" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." -msgstr "必须登录才能评论" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +msgstr "您必须登录才能评论。" + +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "现在登陆" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "隐藏" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "%d条评论" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, fuzzy, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "(%d内嵌)" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, fuzzy, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -#, fuzzy -msgid "Use @username inside this text to notify another user." -msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -#, fuzzy -msgid "Set changeset status" -msgstr "修订集状态" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -#, fuzzy -msgid "No change" -msgstr "无变更" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -#, fuzzy -msgid "Close" -msgstr "已关闭" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" -msgstr "%s修订集" +msgstr "%s 修订集" #: kallithea/templates/changeset/changeset_range.html:56 msgid "Files affected" msgstr "影响文件" -#: kallithea/templates/changeset/diff_block.html:21 -#: kallithea/templates/files/diff_2way.html:43 -msgid "Show full diff for this file" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 -#: kallithea/templates/files/diff_2way.html:46 -msgid "Show full side-by-side diff for this file" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:38 -msgid "Show inline comments" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:86 +#: kallithea/templates/changeset/diff_block.html:54 #, fuzzy msgid "Deleted" msgstr "删除" -#: kallithea/templates/changeset/diff_block.html:89 +#: kallithea/templates/changeset/diff_block.html:57 #, fuzzy msgid "Renamed" msgstr "读" +#: kallithea/templates/changeset/diff_block.html:66 +#: kallithea/templates/files/diff_2way.html:43 +msgid "Show full diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:69 +#: kallithea/templates/files/diff_2way.html:46 +msgid "Show full side-by-side diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:83 +msgid "Show inline comments" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" -msgstr "无修订" +msgstr "无修订集" #: kallithea/templates/compare/compare_cs.html:8 msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "修订集状态:%s" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" -msgstr "" +msgstr "此列表中首个(最旧)修订集" #: kallithea/templates/compare/compare_cs.html:46 msgid "Last (most recent) changeset in this list" -msgstr "" +msgstr "此列表中末个(最近)修订集" #: kallithea/templates/compare/compare_cs.html:48 msgid "Position in this list of changesets" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:76 +msgstr "修订集在此列表中的位置" + +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 -#, fuzzy, python-format +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 +#, python-format msgid "%s changesets" -msgstr "%s修订集" - -#: kallithea/templates/compare/compare_cs.html:100 +msgstr "%s 修订集" + +#: kallithea/templates/compare/compare_cs.html:109 #, fuzzy msgid "behind" msgstr "重新索引" @@ -5200,27 +5175,27 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "显示%s个提交" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5234,7 +5209,7 @@ #: kallithea/templates/data_table/_dt_elements.html:98 msgid "No changesets yet" -msgstr "无修订" +msgstr "尚无任何修订集" #: kallithea/templates/data_table/_dt_elements.html:105 #: kallithea/templates/data_table/_dt_elements.html:107 @@ -5277,21 +5252,26 @@ #: kallithea/templates/email_templates/password_reset.html:6 #, fuzzy -#| msgid "We received a request to create a new password for your account." msgid "We have received a request to reset the password for your account." msgstr "我们收到重置你用户密码的请求。" -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5375,8 +5355,9 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "" +#, fuzzy +msgid "New file type" +msgstr "未知包类型" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5508,10 +5489,19 @@ msgid "Binary file (%s)" msgstr "二进制文件(%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +#, fuzzy +msgid "File is too big to display." msgstr "文件过大,不能显示" +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." +msgstr "" + #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" msgstr "显示注释" @@ -5537,7 +5527,7 @@ #: kallithea/templates/followers/followers_data.html:12 msgid "Started following -" -msgstr "开始关注 - " +msgstr "开始关注 -" #: kallithea/templates/forks/fork.html:5 #, python-format @@ -5780,44 +5770,50 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 #, fuzzy msgid "Pull Request Reviewers" msgstr "拉取请求检视人员" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 #, fuzzy msgid "Remove reviewer" msgstr "检视者" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 +#: kallithea/templates/pullrequests/pullrequest_show.html:261 msgid "Type name of reviewer to add" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:258 +#: kallithea/templates/pullrequests/pullrequest_show.html:269 #, fuzzy msgid "Potential Reviewers" msgstr "%d个检视者" -#: kallithea/templates/pullrequests/pullrequest_show.html:261 +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 #, fuzzy -msgid "Save as New Pull Request" +msgid "Save Updates as New Pull Request" msgstr "新建拉取请求" -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:297 #, fuzzy msgid "Cancel Changes" msgstr "无变更" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 #, fuzzy msgid "Pull Request Content" msgstr "拉取请求" @@ -5829,7 +5825,7 @@ #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 #, fuzzy, python-format -msgid "Pull Requests from %s'" +msgid "Pull Requests from '%s'" msgstr "拉取请求#%s" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 @@ -5934,7 +5930,7 @@ #: kallithea/templates/summary/statistics.html:39 msgid "Stats gathered: " -msgstr "已收集的统计:" +msgstr "已收集的统计: " #: kallithea/templates/summary/statistics.html:89 #: kallithea/templates/summary/summary.html:349 @@ -6124,7 +6120,9 @@ #~ msgstr "没有文件" #~ msgid "" -#~ msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户" +#~ "_: \n" +#~ "" +#~ msgstr "" #~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s" #~ msgstr "" @@ -6348,27 +6346,6 @@ #~ msgid "owner" #~ msgstr "所有者" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "密码已经成功重置,新密码已经发送到你的邮箱" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "" @@ -6390,3 +6367,34 @@ #~ msgid "Created by" #~ msgstr "创建于" +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "You can only edit files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "确认清除版本库缓存" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "评论使用%s语法并支持%s" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户" + +#~ msgid "Comment preview" +#~ msgstr "" + +#~ msgid "Preview" +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po --- a/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po Sat Dec 24 00:34:38 2016 +0100 @@ -7,24 +7,23 @@ msgstr "" "Project-Id-Version: Kallithea 0.3\n" "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n" -"POT-Creation-Date: 2015-09-08 10:34+0200\n" +"POT-Creation-Date: 2016-03-14 16:51+0100\n" "PO-Revision-Date: 2015-08-21 15:52+0200\n" "Last-Translator: EriCSN Chang \n" "Language-Team: Chinese (Taiwan) " "\n" -"Language: zh_TW\n" +"Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 2.4-dev\n" - -#: kallithea/controllers/changelog.py:86 -#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512 +"Generated-By: Babel 1.3\n" + +#: kallithea/controllers/changelog.py:85 +#: kallithea/controllers/pullrequests.py:240 kallithea/lib/base.py:515 msgid "There are no changesets yet" msgstr "" -#: kallithea/controllers/changelog.py:166 +#: kallithea/controllers/changelog.py:164 #: kallithea/controllers/admin/permissions.py:61 #: kallithea/controllers/admin/permissions.py:65 #: kallithea/controllers/admin/permissions.py:69 @@ -36,35 +35,29 @@ msgid "None" msgstr "無" -#: kallithea/controllers/changelog.py:169 kallithea/controllers/files.py:196 +#: kallithea/controllers/changelog.py:167 kallithea/controllers/files.py:198 msgid "(closed)" msgstr "(已關閉)" -#: kallithea/controllers/changeset.py:89 +#: kallithea/controllers/changeset.py:88 msgid "Show whitespace" msgstr "顯示空格" -#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103 +#: kallithea/controllers/changeset.py:95 kallithea/controllers/changeset.py:102 #: kallithea/templates/files/diff_2way.html:55 msgid "Ignore whitespace" msgstr "忽略空格" -#: kallithea/controllers/changeset.py:169 +#: kallithea/controllers/changeset.py:168 #, fuzzy, python-format msgid "Increase diff context to %(num)s lines" msgstr "增加 diff 上下文至 %(num) 行" -#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96 -#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742 +#: kallithea/controllers/changeset.py:233 kallithea/controllers/files.py:97 +#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:744 msgid "Such revision does not exist for this repository" msgstr "" -#: kallithea/controllers/changeset.py:383 -msgid "" -"Changing status on a changeset associated with a closed pull request is " -"not allowed" -msgstr "" - #: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41 msgid "Select changeset" msgstr "" @@ -117,10 +110,10 @@ #: kallithea/controllers/feed.py:87 #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Changeset was too big and was cut off..." msgstr "" @@ -129,111 +122,111 @@ msgid "%s committed on %s" msgstr "" -#: kallithea/controllers/files.py:91 +#: kallithea/controllers/files.py:92 msgid "Click here to add new file" msgstr "" -#: kallithea/controllers/files.py:92 +#: kallithea/controllers/files.py:93 #, python-format msgid "There are no files yet. %s" msgstr "" -#: kallithea/controllers/files.py:193 +#: kallithea/controllers/files.py:195 #, python-format msgid "%s at %s" msgstr "" -#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365 -#: kallithea/controllers/files.py:432 +#: kallithea/controllers/files.py:307 kallithea/controllers/files.py:367 +#: kallithea/controllers/files.py:434 #, python-format msgid "This repository has been locked by %s on %s" msgstr "" -#: kallithea/controllers/files.py:317 -msgid "You can only delete files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:328 +#: kallithea/controllers/files.py:319 +msgid "You can only delete files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:330 #, python-format msgid "Deleted file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:350 +#: kallithea/controllers/files.py:352 #, python-format msgid "Successfully deleted file %s" msgstr "" -#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420 -#: kallithea/controllers/files.py:501 +#: kallithea/controllers/files.py:356 kallithea/controllers/files.py:422 +#: kallithea/controllers/files.py:503 msgid "Error occurred during commit" msgstr "" -#: kallithea/controllers/files.py:377 -msgid "You can only edit files with revision being a valid branch " -msgstr "" - -#: kallithea/controllers/files.py:391 +#: kallithea/controllers/files.py:379 +msgid "You can only edit files with revision being a valid branch" +msgstr "" + +#: kallithea/controllers/files.py:393 #, python-format msgid "Edited file %s via Kallithea" msgstr "" -#: kallithea/controllers/files.py:407 +#: kallithea/controllers/files.py:409 msgid "No changes" msgstr "沒有修改" -#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490 +#: kallithea/controllers/files.py:418 kallithea/controllers/files.py:492 #, python-format msgid "Successfully committed to %s" msgstr "成功遞交至 %s" -#: kallithea/controllers/files.py:443 +#: kallithea/controllers/files.py:445 msgid "Added file via Kallithea" msgstr "" -#: kallithea/controllers/files.py:464 +#: kallithea/controllers/files.py:466 msgid "No content" msgstr "" -#: kallithea/controllers/files.py:468 +#: kallithea/controllers/files.py:470 msgid "No filename" msgstr "" -#: kallithea/controllers/files.py:493 +#: kallithea/controllers/files.py:495 msgid "Location must be relative path and must not contain .. in path" msgstr "" -#: kallithea/controllers/files.py:526 +#: kallithea/controllers/files.py:528 msgid "Downloads disabled" msgstr "" -#: kallithea/controllers/files.py:537 +#: kallithea/controllers/files.py:539 #, python-format msgid "Unknown revision %s" msgstr "未知修訂 %s" -#: kallithea/controllers/files.py:539 +#: kallithea/controllers/files.py:541 msgid "Empty repository" msgstr "空的版本庫" -#: kallithea/controllers/files.py:541 +#: kallithea/controllers/files.py:543 msgid "Unknown archive type" msgstr "未知的存檔類型" -#: kallithea/controllers/files.py:771 +#: kallithea/controllers/files.py:773 #: kallithea/templates/changeset/changeset_range.html:9 #: kallithea/templates/email_templates/pull_request.html:15 #: kallithea/templates/pullrequests/pullrequest.html:97 msgid "Changesets" msgstr "變更" -#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176 -#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3 +#: kallithea/controllers/files.py:774 kallithea/controllers/pullrequests.py:175 +#: kallithea/model/scm.py:716 kallithea/templates/switch_to_list.html:3 #: kallithea/templates/branches/branches.html:10 msgid "Branches" msgstr "分支" -#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177 -#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25 +#: kallithea/controllers/files.py:775 kallithea/controllers/pullrequests.py:176 +#: kallithea/model/scm.py:727 kallithea/templates/switch_to_list.html:25 #: kallithea/templates/tags/tags.html:10 msgid "Tags" msgstr "標籤" @@ -247,7 +240,7 @@ msgid "Groups" msgstr "" -#: kallithea/controllers/home.py:89 +#: kallithea/controllers/home.py:94 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106 #: kallithea/templates/admin/repos/repo_add.html:12 #: kallithea/templates/admin/repos/repo_add.html:16 @@ -255,23 +248,27 @@ #: kallithea/templates/admin/users/user_edit_advanced.html:6 #: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77 #: kallithea/templates/base/base.html:124 -#: kallithea/templates/base/base.html:390 -#: kallithea/templates/base/base.html:562 +#: kallithea/templates/base/base.html:479 +#: kallithea/templates/base/base.html:653 msgid "Repositories" msgstr "版本庫" -#: kallithea/controllers/home.py:130 +#: kallithea/controllers/home.py:139 #: kallithea/templates/files/files_add.html:32 #: kallithea/templates/files/files_delete.html:23 #: kallithea/templates/files/files_edit.html:32 msgid "Branch" msgstr "" -#: kallithea/controllers/home.py:136 +#: kallithea/controllers/home.py:145 kallithea/templates/switch_to_list.html:16 +msgid "Closed Branches" +msgstr "" + +#: kallithea/controllers/home.py:151 msgid "Tag" msgstr "" -#: kallithea/controllers/home.py:142 +#: kallithea/controllers/home.py:157 msgid "Bookmark" msgstr "" @@ -282,163 +279,166 @@ msgstr "開放日誌" #: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157 -#: kallithea/templates/base/base.html:222 +#: kallithea/templates/base/base.html:306 #: kallithea/templates/journal/journal.html:4 #: kallithea/templates/journal/journal.html:12 msgid "Journal" msgstr "日誌" -#: kallithea/controllers/login.py:151 kallithea/controllers/login.py:197 +#: kallithea/controllers/login.py:144 kallithea/controllers/login.py:190 msgid "Bad captcha" msgstr "" -#: kallithea/controllers/login.py:157 -msgid "You have successfully registered into Kallithea" -msgstr "" - -#: kallithea/controllers/login.py:202 +#: kallithea/controllers/login.py:150 +msgid "You have successfully registered with %s" +msgstr "" + +#: kallithea/controllers/login.py:195 #, fuzzy -#| msgid "Your password reset link was sent" msgid "A password reset confirmation code has been sent" msgstr "您的密碼重設連結已寄出" -#: kallithea/controllers/login.py:251 +#: kallithea/controllers/login.py:244 #, fuzzy -#| msgid "Your password reset link was sent" msgid "Invalid password reset token" msgstr "您的密碼重設連結已寄出" -#: kallithea/controllers/login.py:256 +#: kallithea/controllers/login.py:249 #: kallithea/controllers/admin/my_account.py:167 msgid "Successfully updated password" msgstr "" -#: kallithea/controllers/pullrequests.py:124 +#: kallithea/controllers/pullrequests.py:123 #, python-format msgid "%s (closed)" msgstr "" -#: kallithea/controllers/pullrequests.py:152 +#: kallithea/controllers/pullrequests.py:151 #: kallithea/templates/changeset/changeset.html:12 #: kallithea/templates/email_templates/changeset_comment.html:17 msgid "Changeset" msgstr "" +#: kallithea/controllers/pullrequests.py:172 +msgid "Special" +msgstr "" + #: kallithea/controllers/pullrequests.py:173 -msgid "Special" -msgstr "" - -#: kallithea/controllers/pullrequests.py:174 msgid "Peer branches" msgstr "" -#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826 +#: kallithea/controllers/pullrequests.py:174 kallithea/model/scm.py:722 #: kallithea/templates/switch_to_list.html:38 #: kallithea/templates/bookmarks/bookmarks.html:10 msgid "Bookmarks" msgstr "" -#: kallithea/controllers/pullrequests.py:310 +#: kallithea/controllers/pullrequests.py:312 #, python-format msgid "Error creating pull request: %s" msgstr "" -#: kallithea/controllers/pullrequests.py:356 -#: kallithea/controllers/pullrequests.py:503 +#: kallithea/controllers/pullrequests.py:358 +#: kallithea/controllers/pullrequests.py:505 #, fuzzy msgid "No description" msgstr "描述" -#: kallithea/controllers/pullrequests.py:363 +#: kallithea/controllers/pullrequests.py:365 msgid "Successfully opened new pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:366 -#: kallithea/controllers/pullrequests.py:453 -#: kallithea/controllers/pullrequests.py:509 +#: kallithea/controllers/pullrequests.py:368 +#: kallithea/controllers/pullrequests.py:455 +#: kallithea/controllers/pullrequests.py:512 #, python-format msgid "Invalid reviewer \"%s\" specified" msgstr "" -#: kallithea/controllers/pullrequests.py:369 -#: kallithea/controllers/pullrequests.py:456 +#: kallithea/controllers/pullrequests.py:371 +#: kallithea/controllers/pullrequests.py:458 msgid "Error occurred while creating pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:401 +#: kallithea/controllers/pullrequests.py:403 msgid "Missing changesets since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:408 +#: kallithea/controllers/pullrequests.py:410 #, python-format msgid "New changesets on %s %s since the previous pull request:" msgstr "" -#: kallithea/controllers/pullrequests.py:415 +#: kallithea/controllers/pullrequests.py:417 msgid "Ancestor didn't change - show diff since previous version:" msgstr "" -#: kallithea/controllers/pullrequests.py:422 +#: kallithea/controllers/pullrequests.py:424 #, python-format msgid "" "This pull request is based on another %s revision and there is no simple " "diff." msgstr "" -#: kallithea/controllers/pullrequests.py:424 +#: kallithea/controllers/pullrequests.py:426 #, python-format msgid "No changes found on %s %s since previous version." msgstr "" -#: kallithea/controllers/pullrequests.py:462 +#: kallithea/controllers/pullrequests.py:464 #, python-format msgid "Closed, replaced by %s ." msgstr "" -#: kallithea/controllers/pullrequests.py:470 +#: kallithea/controllers/pullrequests.py:472 msgid "Pull request update created" msgstr "" -#: kallithea/controllers/pullrequests.py:513 +#: kallithea/controllers/pullrequests.py:516 msgid "Pull request updated" msgstr "" -#: kallithea/controllers/pullrequests.py:528 +#: kallithea/controllers/pullrequests.py:531 msgid "Successfully deleted pull request" msgstr "" -#: kallithea/controllers/pullrequests.py:594 +#: kallithea/controllers/pullrequests.py:597 #, python-format msgid "This pull request has already been merged to %s." msgstr "" -#: kallithea/controllers/pullrequests.py:596 +#: kallithea/controllers/pullrequests.py:599 msgid "This pull request has been closed and can not be updated." msgstr "" -#: kallithea/controllers/pullrequests.py:614 -#, python-format -msgid "This pull request can be updated with changes on %s:" -msgstr "" - #: kallithea/controllers/pullrequests.py:617 +#, python-format +msgid "The following changes are available on %s:" +msgstr "" + +#: kallithea/controllers/pullrequests.py:621 msgid "No changesets found for updating this pull request." msgstr "" -#: kallithea/controllers/pullrequests.py:625 +#: kallithea/controllers/pullrequests.py:629 #, python-format msgid "Note: Branch %s has another head: %s." msgstr "" -#: kallithea/controllers/pullrequests.py:631 +#: kallithea/controllers/pullrequests.py:635 msgid "Git pull requests don't support updates yet." msgstr "" -#: kallithea/controllers/pullrequests.py:722 -msgid "No permission to change pull request status" -msgstr "" - #: kallithea/controllers/pullrequests.py:727 +msgid "No permission to change pull request status" +msgstr "" + +#: kallithea/controllers/pullrequests.py:738 +#, fuzzy, python-format +msgid "Successfully deleted pull request %s" +msgstr "成功遞交至 %s" + +#: kallithea/controllers/pullrequests.py:748 #, fuzzy msgid "Closing." msgstr "使用中" @@ -455,12 +455,12 @@ msgid "An error occurred during search operation." msgstr "" -#: kallithea/controllers/summary.py:180 +#: kallithea/controllers/summary.py:181 #: kallithea/templates/summary/summary.html:384 msgid "No data ready yet" msgstr "" -#: kallithea/controllers/summary.py:183 +#: kallithea/controllers/summary.py:184 #: kallithea/templates/summary/summary.html:98 msgid "Statistics are disabled for this repository" msgstr "這個版本庫的統計功能已停用" @@ -481,65 +481,65 @@ msgid "Error occurred during update of defaults" msgstr "" -#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/gists.py:58 #: kallithea/controllers/admin/my_account.py:243 +#: kallithea/controllers/admin/users.py:284 +msgid "Forever" +msgstr "" + +#: kallithea/controllers/admin/gists.py:59 +#: kallithea/controllers/admin/my_account.py:244 #: kallithea/controllers/admin/users.py:285 -msgid "Forever" +msgid "5 minutes" msgstr "" #: kallithea/controllers/admin/gists.py:60 -#: kallithea/controllers/admin/my_account.py:244 +#: kallithea/controllers/admin/my_account.py:245 #: kallithea/controllers/admin/users.py:286 -msgid "5 minutes" +msgid "1 hour" msgstr "" #: kallithea/controllers/admin/gists.py:61 -#: kallithea/controllers/admin/my_account.py:245 +#: kallithea/controllers/admin/my_account.py:246 #: kallithea/controllers/admin/users.py:287 -msgid "1 hour" +msgid "1 day" msgstr "" #: kallithea/controllers/admin/gists.py:62 -#: kallithea/controllers/admin/my_account.py:246 +#: kallithea/controllers/admin/my_account.py:247 #: kallithea/controllers/admin/users.py:288 -msgid "1 day" -msgstr "" - -#: kallithea/controllers/admin/gists.py:63 -#: kallithea/controllers/admin/my_account.py:247 -#: kallithea/controllers/admin/users.py:289 msgid "1 month" msgstr "" -#: kallithea/controllers/admin/gists.py:67 +#: kallithea/controllers/admin/gists.py:66 #: kallithea/controllers/admin/my_account.py:249 -#: kallithea/controllers/admin/users.py:291 +#: kallithea/controllers/admin/users.py:290 msgid "Lifetime" msgstr "" -#: kallithea/controllers/admin/gists.py:146 +#: kallithea/controllers/admin/gists.py:145 msgid "Error occurred during gist creation" msgstr "" -#: kallithea/controllers/admin/gists.py:184 +#: kallithea/controllers/admin/gists.py:183 #, python-format msgid "Deleted gist %s" msgstr "" -#: kallithea/controllers/admin/gists.py:233 +#: kallithea/controllers/admin/gists.py:232 #, fuzzy msgid "Unmodified" msgstr "最後修改" -#: kallithea/controllers/admin/gists.py:262 +#: kallithea/controllers/admin/gists.py:261 msgid "Successfully updated gist content" msgstr "" -#: kallithea/controllers/admin/gists.py:267 +#: kallithea/controllers/admin/gists.py:266 msgid "Successfully updated gist data" msgstr "" -#: kallithea/controllers/admin/gists.py:270 +#: kallithea/controllers/admin/gists.py:269 #, python-format msgid "Error occurred during update of gist %s" msgstr "" @@ -554,7 +554,7 @@ msgstr "您的帳號已更新完成" #: kallithea/controllers/admin/my_account.py:144 -#: kallithea/controllers/admin/users.py:202 +#: kallithea/controllers/admin/users.py:201 #, python-format msgid "Error occurred during update of user %s" msgstr "" @@ -564,33 +564,33 @@ msgstr "" #: kallithea/controllers/admin/my_account.py:220 -#: kallithea/controllers/admin/users.py:415 +#: kallithea/controllers/admin/users.py:414 #, python-format msgid "Added email %s to user" msgstr "" #: kallithea/controllers/admin/my_account.py:226 -#: kallithea/controllers/admin/users.py:421 +#: kallithea/controllers/admin/users.py:420 msgid "An error occurred during email saving" msgstr "" #: kallithea/controllers/admin/my_account.py:235 -#: kallithea/controllers/admin/users.py:433 +#: kallithea/controllers/admin/users.py:432 msgid "Removed email from user" msgstr "" #: kallithea/controllers/admin/my_account.py:259 -#: kallithea/controllers/admin/users.py:308 +#: kallithea/controllers/admin/users.py:307 msgid "API key successfully created" msgstr "" #: kallithea/controllers/admin/my_account.py:271 -#: kallithea/controllers/admin/users.py:321 +#: kallithea/controllers/admin/users.py:320 msgid "API key successfully reset" msgstr "" #: kallithea/controllers/admin/my_account.py:275 -#: kallithea/controllers/admin/users.py:325 +#: kallithea/controllers/admin/users.py:324 msgid "API key successfully deleted" msgstr "" @@ -640,10 +640,10 @@ #: kallithea/templates/admin/users/user_edit_profile.html:105 #: kallithea/templates/admin/users/users.html:10 #: kallithea/templates/admin/users/users.html:55 -#: kallithea/templates/base/base.html:252 -#: kallithea/templates/base/base.html:253 -#: kallithea/templates/base/base.html:259 -#: kallithea/templates/base/base.html:260 +#: kallithea/templates/base/base.html:336 +#: kallithea/templates/base/base.html:337 +#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:344 #: kallithea/templates/base/perms_summary.html:17 msgid "Admin" msgstr "管理" @@ -674,7 +674,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1701 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1705 msgid "Manual activation of external account" msgstr "" @@ -686,7 +686,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1702 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1706 msgid "Automatic activation of external account" msgstr "" @@ -707,244 +707,244 @@ msgid "Error occurred during update of permissions" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:188 +#: kallithea/controllers/admin/repo_groups.py:187 #, python-format msgid "Error occurred during creation of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:193 +#: kallithea/controllers/admin/repo_groups.py:192 #, python-format msgid "Created repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:250 +#: kallithea/controllers/admin/repo_groups.py:249 #, python-format msgid "Updated repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:266 +#: kallithea/controllers/admin/repo_groups.py:265 #, python-format msgid "Error occurred during update of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:284 +#: kallithea/controllers/admin/repo_groups.py:283 #, python-format msgid "This group contains %s repositories and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:291 +#: kallithea/controllers/admin/repo_groups.py:290 #, python-format msgid "This group contains %s subgroups and cannot be deleted" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:297 +#: kallithea/controllers/admin/repo_groups.py:296 #, python-format msgid "Removed repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:302 +#: kallithea/controllers/admin/repo_groups.py:301 #, python-format msgid "Error occurred during deletion of repository group %s" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:405 -#: kallithea/controllers/admin/repo_groups.py:440 +#: kallithea/controllers/admin/repo_groups.py:404 +#: kallithea/controllers/admin/repo_groups.py:439 #: kallithea/controllers/admin/user_groups.py:340 msgid "Cannot revoke permission for yourself as admin" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:420 +#: kallithea/controllers/admin/repo_groups.py:419 msgid "Repository group permissions updated" msgstr "" -#: kallithea/controllers/admin/repo_groups.py:457 -#: kallithea/controllers/admin/repos.py:398 +#: kallithea/controllers/admin/repo_groups.py:456 +#: kallithea/controllers/admin/repos.py:397 #: kallithea/controllers/admin/user_groups.py:352 msgid "An error occurred during revoking of permission" msgstr "" -#: kallithea/controllers/admin/repos.py:152 +#: kallithea/controllers/admin/repos.py:151 #, python-format msgid "Error creating repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:213 +#: kallithea/controllers/admin/repos.py:212 #, python-format msgid "Created repository %s from %s" msgstr "" -#: kallithea/controllers/admin/repos.py:222 +#: kallithea/controllers/admin/repos.py:221 #, python-format msgid "Forked repository %s as %s" msgstr "" -#: kallithea/controllers/admin/repos.py:225 +#: kallithea/controllers/admin/repos.py:224 #, python-format msgid "Created repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:262 +#: kallithea/controllers/admin/repos.py:261 #, python-format msgid "Repository %s updated successfully" msgstr "版本庫 %s 更新完成" -#: kallithea/controllers/admin/repos.py:283 +#: kallithea/controllers/admin/repos.py:282 #, python-format msgid "Error occurred during update of repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:310 +#: kallithea/controllers/admin/repos.py:309 #, python-format msgid "Detached %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:313 +#: kallithea/controllers/admin/repos.py:312 #, python-format msgid "Deleted %s forks" msgstr "" -#: kallithea/controllers/admin/repos.py:318 +#: kallithea/controllers/admin/repos.py:317 #, python-format msgid "Deleted repository %s" msgstr "" -#: kallithea/controllers/admin/repos.py:321 +#: kallithea/controllers/admin/repos.py:320 #, python-format msgid "Cannot delete repository %s which still has forks" msgstr "" -#: kallithea/controllers/admin/repos.py:326 +#: kallithea/controllers/admin/repos.py:325 #, python-format msgid "An error occurred during deletion of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:374 +#: kallithea/controllers/admin/repos.py:373 msgid "Repository permissions updated" msgstr "" -#: kallithea/controllers/admin/repos.py:430 +#: kallithea/controllers/admin/repos.py:429 msgid "An error occurred during creation of field" msgstr "" -#: kallithea/controllers/admin/repos.py:444 +#: kallithea/controllers/admin/repos.py:443 msgid "An error occurred during removal of field" msgstr "" -#: kallithea/controllers/admin/repos.py:460 +#: kallithea/controllers/admin/repos.py:459 msgid "-- Not a fork --" msgstr "" -#: kallithea/controllers/admin/repos.py:491 +#: kallithea/controllers/admin/repos.py:490 msgid "Updated repository visibility in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:495 +#: kallithea/controllers/admin/repos.py:494 msgid "An error occurred during setting this repository in public journal" msgstr "" -#: kallithea/controllers/admin/repos.py:512 +#: kallithea/controllers/admin/repos.py:511 msgid "Nothing" msgstr "" -#: kallithea/controllers/admin/repos.py:514 +#: kallithea/controllers/admin/repos.py:513 #, python-format msgid "Marked repository %s as fork of %s" msgstr "" -#: kallithea/controllers/admin/repos.py:521 +#: kallithea/controllers/admin/repos.py:520 msgid "An error occurred during this operation" msgstr "" -#: kallithea/controllers/admin/repos.py:537 -#: kallithea/controllers/admin/repos.py:564 +#: kallithea/controllers/admin/repos.py:536 +#: kallithea/controllers/admin/repos.py:563 #, fuzzy msgid "Repository has been locked" msgstr "" -#: kallithea/controllers/admin/repos.py:540 -#: kallithea/controllers/admin/repos.py:561 +#: kallithea/controllers/admin/repos.py:539 +#: kallithea/controllers/admin/repos.py:560 #, fuzzy msgid "Repository has been unlocked" msgstr "" -#: kallithea/controllers/admin/repos.py:543 -#: kallithea/controllers/admin/repos.py:568 +#: kallithea/controllers/admin/repos.py:542 +#: kallithea/controllers/admin/repos.py:567 msgid "An error occurred during unlocking" msgstr "" -#: kallithea/controllers/admin/repos.py:582 +#: kallithea/controllers/admin/repos.py:581 msgid "Cache invalidation successful" msgstr "" -#: kallithea/controllers/admin/repos.py:586 +#: kallithea/controllers/admin/repos.py:585 msgid "An error occurred during cache invalidation" msgstr "" -#: kallithea/controllers/admin/repos.py:601 +#: kallithea/controllers/admin/repos.py:600 msgid "Pulled from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:604 +#: kallithea/controllers/admin/repos.py:603 msgid "An error occurred during pull from remote location" msgstr "" -#: kallithea/controllers/admin/repos.py:637 +#: kallithea/controllers/admin/repos.py:636 msgid "An error occurred during deletion of repository stats" msgstr "" -#: kallithea/controllers/admin/settings.py:170 +#: kallithea/controllers/admin/settings.py:141 msgid "Updated VCS settings" msgstr "" -#: kallithea/controllers/admin/settings.py:174 +#: kallithea/controllers/admin/settings.py:145 msgid "" "Unable to activate hgsubversion support. The \"hgsubversion\" library is " "missing" msgstr "" -#: kallithea/controllers/admin/settings.py:180 -#: kallithea/controllers/admin/settings.py:277 +#: kallithea/controllers/admin/settings.py:151 +#: kallithea/controllers/admin/settings.py:248 msgid "Error occurred while updating application settings" msgstr "" -#: kallithea/controllers/admin/settings.py:216 +#: kallithea/controllers/admin/settings.py:187 #, python-format msgid "Repositories successfully rescanned. Added: %s. Removed: %s." msgstr "" -#: kallithea/controllers/admin/settings.py:273 +#: kallithea/controllers/admin/settings.py:244 msgid "Updated application settings" msgstr "更新應用設定" -#: kallithea/controllers/admin/settings.py:330 +#: kallithea/controllers/admin/settings.py:301 msgid "Updated visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:335 +#: kallithea/controllers/admin/settings.py:306 msgid "Error occurred during updating visualisation settings" msgstr "" -#: kallithea/controllers/admin/settings.py:361 +#: kallithea/controllers/admin/settings.py:332 msgid "Please enter email address" msgstr "" -#: kallithea/controllers/admin/settings.py:376 +#: kallithea/controllers/admin/settings.py:347 msgid "Send email task created" msgstr "" -#: kallithea/controllers/admin/settings.py:407 +#: kallithea/controllers/admin/settings.py:378 msgid "Added new hook" msgstr "新增hook" -#: kallithea/controllers/admin/settings.py:421 +#: kallithea/controllers/admin/settings.py:392 msgid "Updated hooks" msgstr "更新hook" -#: kallithea/controllers/admin/settings.py:425 +#: kallithea/controllers/admin/settings.py:396 msgid "Error occurred during hook creation" msgstr "" -#: kallithea/controllers/admin/settings.py:451 +#: kallithea/controllers/admin/settings.py:422 msgid "Whoosh reindex task scheduled" msgstr "Whoosh 重新索引工作排程" @@ -985,76 +985,80 @@ msgstr "" #: kallithea/controllers/admin/user_groups.py:440 -#: kallithea/controllers/admin/users.py:384 +#: kallithea/controllers/admin/users.py:383 msgid "Updated permissions" msgstr "" #: kallithea/controllers/admin/user_groups.py:444 -#: kallithea/controllers/admin/users.py:388 +#: kallithea/controllers/admin/users.py:387 msgid "An error occurred during permissions saving" msgstr "" -#: kallithea/controllers/admin/users.py:134 +#: kallithea/controllers/admin/users.py:133 #, python-format msgid "Created user %s" msgstr "" -#: kallithea/controllers/admin/users.py:149 +#: kallithea/controllers/admin/users.py:148 #, python-format msgid "Error occurred during creation of user %s" msgstr "" -#: kallithea/controllers/admin/users.py:182 +#: kallithea/controllers/admin/users.py:181 msgid "User updated successfully" msgstr "使用者更新完成" -#: kallithea/controllers/admin/users.py:218 +#: kallithea/controllers/admin/users.py:217 msgid "Successfully deleted user" msgstr "" -#: kallithea/controllers/admin/users.py:223 +#: kallithea/controllers/admin/users.py:222 msgid "An error occurred during deletion of user" msgstr "" -#: kallithea/controllers/admin/users.py:236 +#: kallithea/controllers/admin/users.py:235 msgid "The default user cannot be edited" msgstr "" -#: kallithea/controllers/admin/users.py:463 +#: kallithea/controllers/admin/users.py:462 #, python-format msgid "Added IP address %s to user whitelist" msgstr "" -#: kallithea/controllers/admin/users.py:469 +#: kallithea/controllers/admin/users.py:468 msgid "An error occurred while adding IP address" msgstr "" -#: kallithea/controllers/admin/users.py:483 +#: kallithea/controllers/admin/users.py:482 msgid "Removed IP address from user whitelist" msgstr "" -#: kallithea/lib/auth.py:743 +#: kallithea/lib/auth.py:737 #, python-format msgid "IP %s not allowed" msgstr "" -#: kallithea/lib/auth.py:756 +#: kallithea/lib/auth.py:750 msgid "Invalid API key" msgstr "" -#: kallithea/lib/auth.py:812 +#: kallithea/lib/auth.py:768 +msgid "CSRF token leak has been detected - all form tokens have been expired" +msgstr "" + +#: kallithea/lib/auth.py:813 msgid "You need to be a registered user to perform this action" msgstr "您必須是註冊使用者才能執行這個動作" -#: kallithea/lib/auth.py:844 +#: kallithea/lib/auth.py:843 msgid "You need to be signed in to view this page" msgstr "您必須登入後才能瀏覽這個頁面" -#: kallithea/lib/base.py:490 +#: kallithea/lib/base.py:493 msgid "Repository not found in the filesystem" msgstr "" -#: kallithea/lib/base.py:516 kallithea/lib/helpers.py:622 +#: kallithea/lib/base.py:519 kallithea/lib/helpers.py:623 msgid "Changeset not found" msgstr "" @@ -1070,125 +1074,125 @@ msgid "No changes detected" msgstr "尚未有任何變更" -#: kallithea/lib/helpers.py:609 +#: kallithea/lib/helpers.py:610 #, python-format msgid "Deleted branch: %s" msgstr "" -#: kallithea/lib/helpers.py:611 +#: kallithea/lib/helpers.py:612 #, python-format msgid "Created tag: %s" msgstr "" -#: kallithea/lib/helpers.py:671 +#: kallithea/lib/helpers.py:672 #, python-format msgid "Show all combined changesets %s->%s" msgstr "" -#: kallithea/lib/helpers.py:677 +#: kallithea/lib/helpers.py:678 msgid "Compare view" msgstr "" -#: kallithea/lib/helpers.py:696 -msgid "and" -msgstr "和" - #: kallithea/lib/helpers.py:697 +msgid "and" +msgstr "和" + +#: kallithea/lib/helpers.py:698 #, python-format msgid "%s more" msgstr "" -#: kallithea/lib/helpers.py:698 kallithea/templates/changelog/changelog.html:44 +#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44 msgid "revisions" msgstr "修訂" -#: kallithea/lib/helpers.py:722 +#: kallithea/lib/helpers.py:723 #, fuzzy, python-format msgid "Fork name %s" msgstr "分支名稱" -#: kallithea/lib/helpers.py:742 +#: kallithea/lib/helpers.py:743 #, fuzzy, python-format msgid "Pull request %s" msgstr "文件內容" -#: kallithea/lib/helpers.py:752 +#: kallithea/lib/helpers.py:753 msgid "[deleted] repository" msgstr "" -#: kallithea/lib/helpers.py:754 kallithea/lib/helpers.py:766 +#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767 msgid "[created] repository" msgstr "" -#: kallithea/lib/helpers.py:756 +#: kallithea/lib/helpers.py:757 msgid "[created] repository as fork" msgstr "" -#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:768 +#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769 msgid "[forked] repository" msgstr "" -#: kallithea/lib/helpers.py:760 kallithea/lib/helpers.py:770 +#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771 msgid "[updated] repository" msgstr "" -#: kallithea/lib/helpers.py:762 +#: kallithea/lib/helpers.py:763 msgid "[downloaded] archive from repository" msgstr "" -#: kallithea/lib/helpers.py:764 +#: kallithea/lib/helpers.py:765 msgid "[delete] repository" msgstr "" -#: kallithea/lib/helpers.py:772 +#: kallithea/lib/helpers.py:773 msgid "[created] user" msgstr "" -#: kallithea/lib/helpers.py:774 +#: kallithea/lib/helpers.py:775 msgid "[updated] user" msgstr "" -#: kallithea/lib/helpers.py:776 +#: kallithea/lib/helpers.py:777 msgid "[created] user group" msgstr "" -#: kallithea/lib/helpers.py:778 +#: kallithea/lib/helpers.py:779 msgid "[updated] user group" msgstr "" -#: kallithea/lib/helpers.py:780 +#: kallithea/lib/helpers.py:781 msgid "[commented] on revision in repository" msgstr "" -#: kallithea/lib/helpers.py:782 +#: kallithea/lib/helpers.py:783 msgid "[commented] on pull request for" msgstr "" -#: kallithea/lib/helpers.py:784 +#: kallithea/lib/helpers.py:785 msgid "[closed] pull request for" msgstr "" -#: kallithea/lib/helpers.py:786 +#: kallithea/lib/helpers.py:787 msgid "[pushed] into" msgstr "" -#: kallithea/lib/helpers.py:788 +#: kallithea/lib/helpers.py:789 msgid "[committed via Kallithea] into repository" msgstr "" -#: kallithea/lib/helpers.py:790 +#: kallithea/lib/helpers.py:791 msgid "[pulled from remote] into repository" msgstr "" -#: kallithea/lib/helpers.py:792 +#: kallithea/lib/helpers.py:793 msgid "[pulled] from" msgstr "" -#: kallithea/lib/helpers.py:794 +#: kallithea/lib/helpers.py:795 msgid "[started following] repository" msgstr "" -#: kallithea/lib/helpers.py:796 +#: kallithea/lib/helpers.py:797 msgid "[stopped following] repository" msgstr "" @@ -1198,8 +1202,8 @@ msgstr "" #: kallithea/lib/helpers.py:1128 -#: kallithea/templates/compare/compare_diff.html:65 -#: kallithea/templates/pullrequests/pullrequest_show.html:326 +#: kallithea/templates/compare/compare_diff.html:71 +#: kallithea/templates/pullrequests/pullrequest_show.html:337 msgid "No files" msgstr "" @@ -1223,7 +1227,7 @@ msgid "chmod" msgstr "" -#: kallithea/lib/helpers.py:1444 +#: kallithea/lib/helpers.py:1469 #, python-format msgid "" "%s repository is not mapped to db perhaps it was created or renamed from " @@ -1231,63 +1235,63 @@ "repositories" msgstr "" -#: kallithea/lib/utils2.py:415 +#: kallithea/lib/utils2.py:434 #, python-format msgid "%d year" msgid_plural "%d years" msgstr[0] "" -#: kallithea/lib/utils2.py:416 +#: kallithea/lib/utils2.py:435 #, python-format msgid "%d month" msgid_plural "%d months" msgstr[0] "" -#: kallithea/lib/utils2.py:417 +#: kallithea/lib/utils2.py:436 #, python-format msgid "%d day" msgid_plural "%d days" msgstr[0] "" -#: kallithea/lib/utils2.py:418 +#: kallithea/lib/utils2.py:437 #, python-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" -#: kallithea/lib/utils2.py:419 +#: kallithea/lib/utils2.py:438 #, python-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" -#: kallithea/lib/utils2.py:420 +#: kallithea/lib/utils2.py:439 #, python-format msgid "%d second" msgid_plural "%d seconds" msgstr[0] "" -#: kallithea/lib/utils2.py:436 +#: kallithea/lib/utils2.py:455 #, python-format msgid "in %s" msgstr "" -#: kallithea/lib/utils2.py:438 +#: kallithea/lib/utils2.py:457 #, python-format msgid "%s ago" msgstr "" -#: kallithea/lib/utils2.py:440 +#: kallithea/lib/utils2.py:459 #, python-format msgid "in %s and %s" msgstr "" -#: kallithea/lib/utils2.py:443 +#: kallithea/lib/utils2.py:462 #, python-format msgid "%s and %s ago" msgstr "" -#: kallithea/lib/utils2.py:446 +#: kallithea/lib/utils2.py:465 msgid "just now" msgstr "現在" @@ -1386,7 +1390,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1665 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1669 msgid "Kallithea Administrator" msgstr "" @@ -1497,7 +1501,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2229 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2237 msgid "Approved" msgstr "" @@ -1512,7 +1516,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2230 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2238 msgid "Rejected" msgstr "" @@ -1539,7 +1543,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1514 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1518 msgid "top level" msgstr "" @@ -1686,7 +1690,7 @@ #: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560 #: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599 #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1697 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1701 msgid "Registration disabled" msgstr "" @@ -1713,12 +1717,12 @@ msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1691 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1695 msgid "Repository creation enabled with write permission to a repository group" msgstr "" #: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646 -#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1692 +#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1696 msgid "Repository creation disabled with write permission to a repository group" msgstr "" @@ -1727,106 +1731,106 @@ msgid "on line %s" msgstr "" -#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169 +#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:170 msgid "[Mention]" msgstr "" -#: kallithea/model/db.py:1667 +#: kallithea/model/db.py:1671 msgid "Default user has no access to new repositories" msgstr "" -#: kallithea/model/db.py:1668 -msgid "Default user has read access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1669 -msgid "Default user has write access to new repositories" -msgstr "" - -#: kallithea/model/db.py:1670 -msgid "Default user has admin access to new repositories" -msgstr "" - #: kallithea/model/db.py:1672 -msgid "Default user has no access to new repository groups" +msgid "Default user has read access to new repositories" msgstr "" #: kallithea/model/db.py:1673 -msgid "Default user has read access to new repository groups" +msgid "Default user has write access to new repositories" msgstr "" #: kallithea/model/db.py:1674 -msgid "Default user has write access to new repository groups" -msgstr "" - -#: kallithea/model/db.py:1675 -msgid "Default user has admin access to new repository groups" +msgid "Default user has admin access to new repositories" +msgstr "" + +#: kallithea/model/db.py:1676 +msgid "Default user has no access to new repository groups" msgstr "" #: kallithea/model/db.py:1677 -msgid "Default user has no access to new user groups" +msgid "Default user has read access to new repository groups" msgstr "" #: kallithea/model/db.py:1678 -msgid "Default user has read access to new user groups" +msgid "Default user has write access to new repository groups" msgstr "" #: kallithea/model/db.py:1679 -msgid "Default user has write access to new user groups" -msgstr "" - -#: kallithea/model/db.py:1680 -msgid "Default user has admin access to new user groups" +msgid "Default user has admin access to new repository groups" +msgstr "" + +#: kallithea/model/db.py:1681 +msgid "Default user has no access to new user groups" msgstr "" #: kallithea/model/db.py:1682 -msgid "Only admins can create repository groups" +msgid "Default user has read access to new user groups" msgstr "" #: kallithea/model/db.py:1683 -msgid "Non-admins can create repository groups" -msgstr "" - -#: kallithea/model/db.py:1685 -msgid "Only admins can create user groups" +msgid "Default user has write access to new user groups" +msgstr "" + +#: kallithea/model/db.py:1684 +msgid "Default user has admin access to new user groups" msgstr "" #: kallithea/model/db.py:1686 -msgid "Non-admins can create user groups" -msgstr "" - -#: kallithea/model/db.py:1688 -msgid "Only admins can create top level repositories" +msgid "Only admins can create repository groups" +msgstr "" + +#: kallithea/model/db.py:1687 +msgid "Non-admins can create repository groups" msgstr "" #: kallithea/model/db.py:1689 +msgid "Only admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1690 +msgid "Non-admins can create user groups" +msgstr "" + +#: kallithea/model/db.py:1692 +msgid "Only admins can create top level repositories" +msgstr "" + +#: kallithea/model/db.py:1693 msgid "Non-admins can create top level repositories" msgstr "" -#: kallithea/model/db.py:1694 +#: kallithea/model/db.py:1698 #, fuzzy msgid "Only admins can fork repositories" msgstr "建立版本庫" -#: kallithea/model/db.py:1695 +#: kallithea/model/db.py:1699 #, fuzzy -msgid "Non-admins can can fork repositories" +msgid "Non-admins can fork repositories" msgstr "建立版本庫" -#: kallithea/model/db.py:1698 +#: kallithea/model/db.py:1702 msgid "User registration with manual account activation" msgstr "" -#: kallithea/model/db.py:1699 +#: kallithea/model/db.py:1703 msgid "User registration with automatic account activation" msgstr "" -#: kallithea/model/db.py:2228 +#: kallithea/model/db.py:2236 #, fuzzy msgid "Not reviewed" msgstr "" -#: kallithea/model/db.py:2231 +#: kallithea/model/db.py:2239 #, fuzzy msgid "Under review" msgstr "" @@ -1849,7 +1853,7 @@ msgid "Enter %(min)i characters or more" msgstr "" -#: kallithea/model/forms.py:160 +#: kallithea/model/forms.py:165 msgid "Name must not contain only digits" msgstr "" @@ -1943,7 +1947,7 @@ msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s" msgstr "" -#: kallithea/model/scm.py:812 +#: kallithea/model/scm.py:708 msgid "latest tip" msgstr "" @@ -1977,15 +1981,15 @@ "owners or remove those user groups: %s" msgstr "" -#: kallithea/model/user.py:360 +#: kallithea/model/user.py:368 msgid "Password reset link" msgstr "" -#: kallithea/model/user.py:408 +#: kallithea/model/user.py:418 msgid "Password reset notification" msgstr "" -#: kallithea/model/user.py:409 +#: kallithea/model/user.py:419 #, python-format msgid "" "The password to your account %s has been changed using password reset " @@ -1996,170 +2000,170 @@ msgid "Value cannot be an empty list" msgstr "" -#: kallithea/model/validators.py:95 +#: kallithea/model/validators.py:96 #, python-format msgid "Username \"%(username)s\" already exists" msgstr "" -#: kallithea/model/validators.py:97 +#: kallithea/model/validators.py:98 #, python-format msgid "Username \"%(username)s\" cannot be used" msgstr "" -#: kallithea/model/validators.py:99 +#: kallithea/model/validators.py:100 msgid "" "Username may only contain alphanumeric characters underscores, periods or" " dashes and must begin with an alphanumeric character or underscore" msgstr "" -#: kallithea/model/validators.py:126 +#: kallithea/model/validators.py:127 msgid "The input is not valid" msgstr "" -#: kallithea/model/validators.py:133 +#: kallithea/model/validators.py:134 #, python-format msgid "Username %(username)s is not valid" msgstr "" -#: kallithea/model/validators.py:152 +#: kallithea/model/validators.py:154 msgid "Invalid user group name" msgstr "" -#: kallithea/model/validators.py:153 -#, python-format -msgid "User group \"%(usergroup)s\" already exists" -msgstr "" - #: kallithea/model/validators.py:155 +#, python-format +msgid "User group \"%(usergroup)s\" already exists" +msgstr "" + +#: kallithea/model/validators.py:157 msgid "" "user group name may only contain alphanumeric characters underscores, " "periods or dashes and must begin with alphanumeric character" msgstr "" -#: kallithea/model/validators.py:193 +#: kallithea/model/validators.py:197 msgid "Cannot assign this group as parent" msgstr "" -#: kallithea/model/validators.py:194 +#: kallithea/model/validators.py:198 #, python-format msgid "Group \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:196 +#: kallithea/model/validators.py:200 #, python-format msgid "Repository with name \"%(group_name)s\" already exists" msgstr "" -#: kallithea/model/validators.py:254 +#: kallithea/model/validators.py:258 msgid "Invalid characters (non-ascii) in password" msgstr "" -#: kallithea/model/validators.py:269 +#: kallithea/model/validators.py:273 msgid "Invalid old password" msgstr "" -#: kallithea/model/validators.py:285 +#: kallithea/model/validators.py:289 msgid "Passwords do not match" msgstr "密碼不相符" -#: kallithea/model/validators.py:300 +#: kallithea/model/validators.py:304 #, fuzzy msgid "Invalid username or password" msgstr "無效的密碼" -#: kallithea/model/validators.py:331 +#: kallithea/model/validators.py:335 msgid "Token mismatch" msgstr "" -#: kallithea/model/validators.py:345 +#: kallithea/model/validators.py:351 #, python-format msgid "Repository name %(repo)s is not allowed" msgstr "" -#: kallithea/model/validators.py:347 +#: kallithea/model/validators.py:353 #, python-format msgid "Repository named %(repo)s already exists" msgstr "" -#: kallithea/model/validators.py:348 +#: kallithea/model/validators.py:354 #, python-format msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\"" msgstr "" -#: kallithea/model/validators.py:350 +#: kallithea/model/validators.py:356 #, python-format msgid "Repository group with name \"%(repo)s\" already exists" msgstr "" -#: kallithea/model/validators.py:465 +#: kallithea/model/validators.py:470 #, fuzzy msgid "Invalid repository URL" msgstr "私有版本庫" -#: kallithea/model/validators.py:466 +#: kallithea/model/validators.py:471 msgid "" "Invalid repository URL. It must be a valid http, https, ssh, svn+http or " "svn+https URL" msgstr "" -#: kallithea/model/validators.py:489 +#: kallithea/model/validators.py:496 msgid "Fork has to be the same type as parent" msgstr "" -#: kallithea/model/validators.py:504 +#: kallithea/model/validators.py:511 msgid "You don't have permissions to create repository in this group" msgstr "" -#: kallithea/model/validators.py:506 +#: kallithea/model/validators.py:513 msgid "no permission to create repository in root location" msgstr "" -#: kallithea/model/validators.py:556 +#: kallithea/model/validators.py:563 msgid "You don't have permissions to create a group in this location" msgstr "" -#: kallithea/model/validators.py:597 +#: kallithea/model/validators.py:604 msgid "This username or user group name is not valid" msgstr "" -#: kallithea/model/validators.py:690 +#: kallithea/model/validators.py:697 msgid "This is not a valid path" msgstr "不是一個有效的路徑" -#: kallithea/model/validators.py:705 +#: kallithea/model/validators.py:714 #, fuzzy msgid "This email address is already in use" msgstr "這個郵件位址已經使用了" -#: kallithea/model/validators.py:725 +#: kallithea/model/validators.py:734 #, python-format msgid "Email address \"%(email)s\" not found" msgstr "" -#: kallithea/model/validators.py:762 +#: kallithea/model/validators.py:771 msgid "" "The LDAP Login attribute of the CN must be specified - this is the name " "of the attribute that is equivalent to \"username\"" msgstr "" -#: kallithea/model/validators.py:774 +#: kallithea/model/validators.py:783 msgid "Please enter a valid IPv4 or IPv6 address" msgstr "" -#: kallithea/model/validators.py:775 +#: kallithea/model/validators.py:784 #, python-format msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)" msgstr "" -#: kallithea/model/validators.py:808 +#: kallithea/model/validators.py:817 msgid "Key name can only consist of letters, underscore, dash or numbers" msgstr "" -#: kallithea/model/validators.py:822 +#: kallithea/model/validators.py:831 msgid "Filename cannot be inside a directory" msgstr "" -#: kallithea/model/validators.py:838 +#: kallithea/model/validators.py:847 #, python-format msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name" msgstr "" @@ -2286,7 +2290,7 @@ #: kallithea/templates/admin/user_groups/user_groups.html:50 #: kallithea/templates/pullrequests/pullrequest_data.html:16 #: kallithea/templates/pullrequests/pullrequest_show.html:156 -#: kallithea/templates/pullrequests/pullrequest_show.html:233 +#: kallithea/templates/pullrequests/pullrequest_show.html:244 #: kallithea/templates/summary/summary.html:134 msgid "Owner" msgstr "擁有者" @@ -2334,7 +2338,7 @@ #: kallithea/templates/index_base.html:144 #: kallithea/templates/admin/my_account/my_account_repos.html:61 #: kallithea/templates/admin/my_account/my_account_watched.html:61 -#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47 +#: kallithea/templates/base/root.html:47 #: kallithea/templates/bookmarks/bookmarks.html:83 #: kallithea/templates/branches/branches.html:83 #: kallithea/templates/journal/journal.html:202 @@ -2344,7 +2348,7 @@ msgstr "" #: kallithea/templates/login.html:5 kallithea/templates/login.html:15 -#: kallithea/templates/base/base.html:326 +#: kallithea/templates/base/base.html:414 msgid "Log In" msgstr "" @@ -2359,7 +2363,7 @@ #: kallithea/templates/admin/users/user_add.html:32 #: kallithea/templates/admin/users/user_edit_profile.html:24 #: kallithea/templates/admin/users/users.html:50 -#: kallithea/templates/base/base.html:302 +#: kallithea/templates/base/base.html:390 #: kallithea/templates/pullrequests/pullrequest_show.html:166 msgid "Username" msgstr "帳號" @@ -2367,7 +2371,7 @@ #: kallithea/templates/login.html:33 kallithea/templates/register.html:33 #: kallithea/templates/admin/my_account/my_account.html:37 #: kallithea/templates/admin/users/user_add.html:41 -#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:399 msgid "Password" msgstr "密碼" @@ -2379,7 +2383,7 @@ msgid "Forgot your password ?" msgstr "忘記您的密碼?" -#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322 +#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:410 msgid "Don't have an account ?" msgstr "沒有帳號?" @@ -2419,8 +2423,6 @@ #: kallithea/templates/password_reset.html:47 #, fuzzy -#| msgid "" "Password reset link will be sent to the email address matching -#| your " "username." msgid "" "A password reset link will be sent to the specified email address if it " "is registered in the system." @@ -2443,13 +2445,11 @@ #: kallithea/templates/password_reset_confirmation.html:39 #, fuzzy -#| msgid "New password" msgid "New Password" msgstr "新密碼" #: kallithea/templates/password_reset_confirmation.html:48 #, fuzzy -#| msgid "New password" msgid "Confirm New Password" msgstr "新密碼" @@ -2508,10 +2508,6 @@ msgid "There are no branches yet" msgstr "沒有任何分支" -#: kallithea/templates/switch_to_list.html:16 -msgid "Closed Branches" -msgstr "" - #: kallithea/templates/switch_to_list.html:32 #: kallithea/templates/tags/tags_data.html:44 msgid "There are no tags yet" @@ -2741,12 +2737,12 @@ msgid "Never" msgstr "擁有者" -#: kallithea/templates/admin/gists/edit.html:145 +#: kallithea/templates/admin/gists/edit.html:146 msgid "Update Gist" msgstr "" -#: kallithea/templates/admin/gists/edit.html:146 -#: kallithea/templates/changeset/changeset_file_comment.html:81 +#: kallithea/templates/admin/gists/edit.html:147 +#: kallithea/templates/changeset/changeset_file_comment.html:105 msgid "Cancel" msgstr "" @@ -2769,7 +2765,7 @@ #: kallithea/templates/admin/gists/index.html:37 #: kallithea/templates/admin/gists/show.html:25 -#: kallithea/templates/base/base.html:237 +#: kallithea/templates/base/base.html:321 msgid "Create New Gist" msgstr "" @@ -2857,7 +2853,8 @@ #: kallithea/templates/admin/settings/settings_hooks.html:36 #: kallithea/templates/admin/users/user_edit_emails.html:19 #: kallithea/templates/admin/users/user_edit_ips.html:22 -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 +#: kallithea/templates/changeset/changeset_file_comment.html:95 #: kallithea/templates/data_table/_dt_elements.html:129 #: kallithea/templates/data_table/_dt_elements.html:157 #: kallithea/templates/data_table/_dt_elements.html:173 @@ -2877,8 +2874,6 @@ #: kallithea/templates/base/perms_summary.html:43 #: kallithea/templates/base/perms_summary.html:79 #: kallithea/templates/base/perms_summary.html:81 -#: kallithea/templates/changeset/changeset_file_comment.html:83 -#: kallithea/templates/changeset/changeset_file_comment.html:192 #: kallithea/templates/data_table/_dt_elements.html:122 #: kallithea/templates/data_table/_dt_elements.html:123 #: kallithea/templates/data_table/_dt_elements.html:150 @@ -2905,13 +2900,12 @@ msgstr "" #: kallithea/templates/admin/gists/show.html:86 -#: kallithea/templates/files/files_source.html:73 msgid "Show as raw" msgstr "" #: kallithea/templates/admin/my_account/my_account.html:5 #: kallithea/templates/admin/my_account/my_account.html:9 -#: kallithea/templates/base/base.html:343 +#: kallithea/templates/base/base.html:431 msgid "My Account" msgstr "我的帳號" @@ -3099,7 +3093,7 @@ msgstr "" #: kallithea/templates/admin/notifications/notifications.html:26 -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 msgid "Pull Requests" msgstr "" @@ -3117,7 +3111,7 @@ msgstr "" #: kallithea/templates/admin/notifications/show_notification.html:9 -#: kallithea/templates/base/base.html:342 +#: kallithea/templates/base/base.html:430 msgid "Notifications" msgstr "" @@ -3318,7 +3312,7 @@ #: kallithea/templates/admin/repos/repo_edit.html:40 #: kallithea/templates/admin/settings/settings.html:11 #: kallithea/templates/admin/user_groups/user_group_edit.html:29 -#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151 +#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:148 #: kallithea/templates/data_table/_dt_elements.html:45 #: kallithea/templates/data_table/_dt_elements.html:49 msgid "Settings" @@ -3590,6 +3584,11 @@ msgid "Unlock Repository" msgstr "公開的版本庫" +#: kallithea/templates/admin/repos/repo_edit_advanced.html:56 +#, python-format +msgid "Locked by %s on %s" +msgstr "" + #: kallithea/templates/admin/repos/repo_edit_advanced.html:60 #, fuzzy msgid "Confirm to lock repository." @@ -3649,11 +3648,6 @@ msgid "Invalidate Repository Cache" msgstr "確認廢止版本庫快取" -#: kallithea/templates/admin/repos/repo_edit_caches.html:4 -#, fuzzy -msgid "Confirm to invalidate repository cache." -msgstr "確認廢止版本庫快取" - #: kallithea/templates/admin/repos/repo_edit_caches.html:7 msgid "" "Manually invalidate cache for this repository. On first access, the " @@ -4403,21 +4397,17 @@ msgid "Files" msgstr "檔案" -#: kallithea/templates/base/base.html:138 -msgid "Switch To" -msgstr "" - -#: kallithea/templates/base/base.html:145 -#: kallithea/templates/base/base.html:147 +#: kallithea/templates/base/base.html:142 +#: kallithea/templates/base/base.html:144 msgid "Options" msgstr "選項" -#: kallithea/templates/base/base.html:155 +#: kallithea/templates/base/base.html:152 #: kallithea/templates/forks/forks_data.html:21 msgid "Compare Fork" msgstr "" -#: kallithea/templates/base/base.html:157 +#: kallithea/templates/base/base.html:154 #: kallithea/templates/bookmarks/bookmarks.html:56 #: kallithea/templates/bookmarks/bookmarks_data.html:13 #: kallithea/templates/branches/branches.html:56 @@ -4427,111 +4417,116 @@ msgid "Compare" msgstr "" -#: kallithea/templates/base/base.html:159 -#: kallithea/templates/base/base.html:247 +#: kallithea/templates/base/base.html:156 +#: kallithea/templates/base/base.html:331 #: kallithea/templates/search/search.html:14 #: kallithea/templates/search/search.html:54 msgid "Search" msgstr "搜尋" -#: kallithea/templates/base/base.html:163 +#: kallithea/templates/base/base.html:160 msgid "Unlock" msgstr "" -#: kallithea/templates/base/base.html:165 +#: kallithea/templates/base/base.html:162 msgid "Lock" msgstr "" -#: kallithea/templates/base/base.html:173 +#: kallithea/templates/base/base.html:170 msgid "Follow" msgstr "" +#: kallithea/templates/base/base.html:171 +msgid "Unfollow" +msgstr "" + #: kallithea/templates/base/base.html:174 -msgid "Unfollow" -msgstr "" - -#: kallithea/templates/base/base.html:177 #: kallithea/templates/data_table/_dt_elements.html:37 #: kallithea/templates/data_table/_dt_elements.html:41 #: kallithea/templates/forks/fork.html:9 msgid "Fork" msgstr "分支" -#: kallithea/templates/base/base.html:178 +#: kallithea/templates/base/base.html:175 #: kallithea/templates/pullrequests/pullrequest.html:88 msgid "Create Pull Request" msgstr "" -#: kallithea/templates/base/base.html:183 +#: kallithea/templates/base/base.html:180 #, python-format msgid "Show Pull Requests for %s" msgstr "" -#: kallithea/templates/base/base.html:221 +#: kallithea/templates/base/base.html:193 +msgid "Switch To" +msgstr "" + +#: kallithea/templates/base/base.html:203 +#: kallithea/templates/base/base.html:485 +msgid "No matches found" +msgstr "" + +#: kallithea/templates/base/base.html:305 msgid "Show recent activity" msgstr "" -#: kallithea/templates/base/base.html:227 -#: kallithea/templates/base/base.html:228 +#: kallithea/templates/base/base.html:311 +#: kallithea/templates/base/base.html:312 msgid "Public journal" msgstr "公開日誌" -#: kallithea/templates/base/base.html:233 +#: kallithea/templates/base/base.html:317 msgid "Show public gists" msgstr "" -#: kallithea/templates/base/base.html:234 +#: kallithea/templates/base/base.html:318 msgid "Gists" msgstr "" -#: kallithea/templates/base/base.html:238 +#: kallithea/templates/base/base.html:322 msgid "All Public Gists" msgstr "" -#: kallithea/templates/base/base.html:240 +#: kallithea/templates/base/base.html:324 msgid "My Public Gists" msgstr "" -#: kallithea/templates/base/base.html:241 +#: kallithea/templates/base/base.html:325 msgid "My Private Gists" msgstr "" -#: kallithea/templates/base/base.html:246 +#: kallithea/templates/base/base.html:330 msgid "Search in repositories" msgstr "" -#: kallithea/templates/base/base.html:269 -#: kallithea/templates/base/base.html:270 +#: kallithea/templates/base/base.html:353 +#: kallithea/templates/base/base.html:354 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10 msgid "My Pull Requests" msgstr "" -#: kallithea/templates/base/base.html:289 +#: kallithea/templates/base/base.html:377 msgid "Not Logged In" msgstr "" -#: kallithea/templates/base/base.html:296 +#: kallithea/templates/base/base.html:384 msgid "Login to Your Account" msgstr "" -#: kallithea/templates/base/base.html:319 +#: kallithea/templates/base/base.html:407 msgid "Forgot password ?" msgstr "忘記密碼?" -#: kallithea/templates/base/base.html:346 +#: kallithea/templates/base/base.html:434 msgid "Log Out" msgstr "登出" -#: kallithea/templates/base/base.html:395 -msgid "No matches found" -msgstr "" - -#: kallithea/templates/base/base.html:524 +#: kallithea/templates/base/base.html:615 msgid "Keyboard shortcuts" msgstr "" -#: kallithea/templates/base/base.html:533 +#: kallithea/templates/base/base.html:624 msgid "Site-wide shortcuts" msgstr "" @@ -4650,6 +4645,7 @@ #: kallithea/templates/base/root.html:35 #: kallithea/templates/changeset/diff_block.html:8 +#: kallithea/templates/changeset/diff_block.html:21 #, fuzzy msgid "Collapse Diff" msgstr "檔案差異" @@ -4760,51 +4756,54 @@ #: kallithea/templates/changelog/changelog_summary_data.html:20 #, python-format msgid "" -"Changeset status: %s\n" +"Changeset status: %s by %s\n" "Click to open associated pull request %s" msgstr "" #: kallithea/templates/changelog/changelog.html:96 -#: kallithea/templates/compare/compare_cs.html:24 -#, python-format -msgid "Changeset status: %s" -msgstr "" - -#: kallithea/templates/changelog/changelog.html:115 +#: kallithea/templates/changelog/changelog_summary_data.html:24 +#, fuzzy, python-format +#| msgid "Set changeset status" +msgid "Changeset status: %s by %s" +msgstr "尚未有任何變更" + +#: kallithea/templates/changelog/changelog.html:116 #: kallithea/templates/compare/compare_cs.html:63 msgid "Expand commit message" msgstr "" -#: kallithea/templates/changelog/changelog.html:124 +#: kallithea/templates/changelog/changelog.html:125 #: kallithea/templates/compare/compare_cs.html:30 msgid "Changeset has comments" msgstr "" -#: kallithea/templates/changelog/changelog.html:134 -#: kallithea/templates/changelog/changelog_summary_data.html:54 +#: kallithea/templates/changelog/changelog.html:135 +#: kallithea/templates/changelog/changelog_summary_data.html:57 #: kallithea/templates/changeset/changeset.html:94 #: kallithea/templates/changeset/changeset_range.html:92 #, python-format msgid "Bookmark %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:140 -#: kallithea/templates/changelog/changelog_summary_data.html:60 +#: kallithea/templates/changelog/changelog.html:141 +#: kallithea/templates/changelog/changelog_summary_data.html:63 #: kallithea/templates/changeset/changeset.html:101 #: kallithea/templates/changeset/changeset_range.html:98 +#: kallithea/templates/compare/compare_cs.html:69 +#: kallithea/templates/pullrequests/pullrequest_show.html:203 #, python-format msgid "Tag %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:145 -#: kallithea/templates/changelog/changelog_summary_data.html:65 +#: kallithea/templates/changelog/changelog.html:146 +#: kallithea/templates/changelog/changelog_summary_data.html:68 #: kallithea/templates/changeset/changeset.html:106 #: kallithea/templates/changeset/changeset_range.html:102 #, python-format msgid "Branch %s" msgstr "" -#: kallithea/templates/changelog/changelog.html:310 +#: kallithea/templates/changelog/changelog.html:311 msgid "There are no changes yet" msgstr "尚未有任何變更" @@ -4820,7 +4819,7 @@ #: kallithea/templates/changelog/changelog_details.html:6 #: kallithea/templates/changeset/changeset.html:79 -#: kallithea/templates/changeset/diff_block.html:79 +#: kallithea/templates/changeset/diff_block.html:47 msgid "Added" msgstr "" @@ -4850,22 +4849,22 @@ msgid "Refs" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:81 +#: kallithea/templates/changelog/changelog_summary_data.html:84 msgid "Add or upload files directly via Kallithea" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:84 +#: kallithea/templates/changelog/changelog_summary_data.html:87 #: kallithea/templates/files/files_add.html:21 #: kallithea/templates/files/files_ypjax.html:9 msgid "Add New File" msgstr "" -#: kallithea/templates/changelog/changelog_summary_data.html:90 +#: kallithea/templates/changelog/changelog_summary_data.html:93 #, fuzzy msgid "Push new repository" msgstr "私有版本庫" -#: kallithea/templates/changelog/changelog_summary_data.html:98 +#: kallithea/templates/changelog/changelog_summary_data.html:101 msgid "Existing repository?" msgstr "" @@ -4883,13 +4882,13 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:50 -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #: kallithea/templates/changeset/changeset_range.html:48 msgid "Changeset status" msgstr "" #: kallithea/templates/changeset/changeset.html:54 -#: kallithea/templates/changeset/diff_block.html:27 +#: kallithea/templates/changeset/diff_block.html:72 #: kallithea/templates/files/diff_2way.html:49 msgid "Raw diff" msgstr "" @@ -4899,7 +4898,7 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:60 -#: kallithea/templates/changeset/diff_block.html:30 +#: kallithea/templates/changeset/diff_block.html:75 #: kallithea/templates/files/diff_2way.html:52 msgid "Download diff" msgstr "" @@ -4927,16 +4926,16 @@ msgstr "" #: kallithea/templates/changeset/changeset.html:166 -#: kallithea/templates/compare/compare_diff.html:54 -#: kallithea/templates/pullrequests/pullrequest_show.html:318 +#: kallithea/templates/compare/compare_diff.html:60 +#: kallithea/templates/pullrequests/pullrequest_show.html:329 #, python-format msgid "%s file changed" msgid_plural "%s files changed" msgstr[0] "" #: kallithea/templates/changeset/changeset.html:168 -#: kallithea/templates/compare/compare_diff.html:56 -#: kallithea/templates/pullrequests/pullrequest_show.html:320 +#: kallithea/templates/compare/compare_diff.html:62 +#: kallithea/templates/pullrequests/pullrequest_show.html:331 #, python-format msgid "%s file changed with %s insertions and %s deletions" msgid_plural "%s files changed with %s insertions and %s deletions" @@ -4944,13 +4943,13 @@ #: kallithea/templates/changeset/changeset.html:182 #: kallithea/templates/changeset/changeset.html:195 -#: kallithea/templates/pullrequests/pullrequest_show.html:339 -#: kallithea/templates/pullrequests/pullrequest_show.html:363 +#: kallithea/templates/pullrequests/pullrequest_show.html:350 +#: kallithea/templates/pullrequests/pullrequest_show.html:372 msgid "Show full diff anyway" msgstr "" -#: kallithea/templates/changeset/changeset.html:247 -#: kallithea/templates/changeset/changeset.html:284 +#: kallithea/templates/changeset/changeset.html:231 +#: kallithea/templates/changeset/changeset.html:268 #, fuzzy msgid "No revisions" msgstr "修訂" @@ -4969,101 +4968,87 @@ msgid "on this changeset" msgstr "沒有修改" -#: kallithea/templates/changeset/changeset_file_comment.html:30 +#: kallithea/templates/changeset/changeset_file_comment.html:31 #, fuzzy msgid "Delete comment?" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:37 +#: kallithea/templates/changeset/changeset_file_comment.html:39 #, fuzzy msgid "Status change" msgstr "多個檔案修改" #: kallithea/templates/changeset/changeset_file_comment.html:59 -msgid "Commenting on line {1}." +msgid "Commenting on line." msgstr "" #: kallithea/templates/changeset/changeset_file_comment.html:60 -#: kallithea/templates/changeset/changeset_file_comment.html:148 -#, python-format -msgid "Comments parsed using %s syntax with %s support." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:62 -msgid "Use @username inside this text to notify another user" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:72 -#: kallithea/templates/changeset/changeset_file_comment.html:184 -msgid "Comment preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:77 +msgid "" +"Comments are in plain text. Use @username inside this text to notify " +"another user." +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:67 +#, fuzzy +msgid "Set changeset status" +msgstr "尚未有任何變更" + +#: kallithea/templates/changeset/changeset_file_comment.html:69 +msgid "Vote for pull request status" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:75 +#, fuzzy +msgid "No change" +msgstr "沒有修改" + +#: kallithea/templates/changeset/changeset_file_comment.html:88 +#, fuzzy +msgid "Finish pull request" +msgstr "文件內容" + +#: kallithea/templates/changeset/changeset_file_comment.html:91 +msgid "Close" +msgstr "" + +#: kallithea/templates/changeset/changeset_file_comment.html:103 msgid "Submitting ..." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:80 -#: kallithea/templates/changeset/changeset_file_comment.html:190 +#: kallithea/templates/changeset/changeset_file_comment.html:104 msgid "Comment" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:82 -#: kallithea/templates/changeset/changeset_file_comment.html:191 -msgid "Preview" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "You need to be logged in to comment." msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:90 +#: kallithea/templates/changeset/changeset_file_comment.html:112 msgid "Login now" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:94 +#: kallithea/templates/changeset/changeset_file_comment.html:116 msgid "Hide" msgstr "" -#: kallithea/templates/changeset/changeset_file_comment.html:106 +#: kallithea/templates/changeset/changeset_file_comment.html:128 #, python-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" -#: kallithea/templates/changeset/changeset_file_comment.html:107 +#: kallithea/templates/changeset/changeset_file_comment.html:129 #, fuzzy, python-format msgid "%d inline" msgid_plural "%d inline" msgstr[0] "" -#: kallithea/templates/changeset/changeset_file_comment.html:108 +#: kallithea/templates/changeset/changeset_file_comment.html:130 #, python-format msgid "%d general" msgid_plural "%d general" msgstr[0] "" -#: kallithea/templates/changeset/changeset_file_comment.html:150 -msgid "Use @username inside this text to notify another user." -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:157 -msgid "Vote for pull request status" -msgstr "" - -#: kallithea/templates/changeset/changeset_file_comment.html:159 -#, fuzzy -msgid "Set changeset status" -msgstr "尚未有任何變更" - -#: kallithea/templates/changeset/changeset_file_comment.html:163 -#, fuzzy -msgid "No change" -msgstr "沒有修改" - -#: kallithea/templates/changeset/changeset_file_comment.html:176 -msgid "Close" -msgstr "" - #: kallithea/templates/changeset/changeset_range.html:5 #, python-format msgid "%s Changesets" @@ -5073,31 +5058,30 @@ msgid "Files affected" msgstr "" -#: kallithea/templates/changeset/diff_block.html:21 -#: kallithea/templates/files/diff_2way.html:43 -msgid "Show full diff for this file" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:24 -#: kallithea/templates/changeset/diff_block.html:98 -#: kallithea/templates/files/diff_2way.html:46 -msgid "Show full side-by-side diff for this file" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:38 -msgid "Show inline comments" -msgstr "" - -#: kallithea/templates/changeset/diff_block.html:86 +#: kallithea/templates/changeset/diff_block.html:54 #, fuzzy msgid "Deleted" msgstr "刪除" -#: kallithea/templates/changeset/diff_block.html:89 +#: kallithea/templates/changeset/diff_block.html:57 #, fuzzy msgid "Renamed" msgstr "讀" +#: kallithea/templates/changeset/diff_block.html:66 +#: kallithea/templates/files/diff_2way.html:43 +msgid "Show full diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:69 +#: kallithea/templates/files/diff_2way.html:46 +msgid "Show full side-by-side diff for this file" +msgstr "" + +#: kallithea/templates/changeset/diff_block.html:83 +msgid "Show inline comments" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:4 msgid "No changesets" msgstr "" @@ -5106,6 +5090,11 @@ msgid "Ancestor" msgstr "" +#: kallithea/templates/compare/compare_cs.html:24 +#, python-format +msgid "Changeset status: %s" +msgstr "" + #: kallithea/templates/compare/compare_cs.html:44 msgid "First (oldest) changeset in this list" msgstr "" @@ -5118,29 +5107,29 @@ msgid "Position in this list of changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:76 +#: kallithea/templates/compare/compare_cs.html:85 msgid "Show merge diff" msgstr "" -#: kallithea/templates/compare/compare_cs.html:86 -#: kallithea/templates/pullrequests/pullrequest_show.html:310 +#: kallithea/templates/compare/compare_cs.html:95 +#: kallithea/templates/pullrequests/pullrequest_show.html:321 msgid "Common ancestor" msgstr "" -#: kallithea/templates/compare/compare_cs.html:90 -msgid "No common ancestor found - repositories are unrelated" -msgstr "" - -#: kallithea/templates/compare/compare_cs.html:98 -msgid "is" -msgstr "" - #: kallithea/templates/compare/compare_cs.html:99 +msgid "No common ancestor found - repositories are unrelated" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:107 +msgid "is" +msgstr "" + +#: kallithea/templates/compare/compare_cs.html:108 #, fuzzy, python-format msgid "%s changesets" msgstr "" -#: kallithea/templates/compare/compare_cs.html:100 +#: kallithea/templates/compare/compare_cs.html:109 #, fuzzy msgid "behind" msgstr "重新索引" @@ -5152,27 +5141,27 @@ msgstr "" #: kallithea/templates/compare/compare_diff.html:13 -#: kallithea/templates/compare/compare_diff.html:35 +#: kallithea/templates/compare/compare_diff.html:41 msgid "Compare Revisions" msgstr "" -#: kallithea/templates/compare/compare_diff.html:33 +#: kallithea/templates/compare/compare_diff.html:39 msgid "Swap" msgstr "" -#: kallithea/templates/compare/compare_diff.html:42 +#: kallithea/templates/compare/compare_diff.html:48 msgid "Compare revisions, branches, bookmarks, or tags." msgstr "" -#: kallithea/templates/compare/compare_diff.html:47 -#: kallithea/templates/pullrequests/pullrequest_show.html:305 +#: kallithea/templates/compare/compare_diff.html:53 +#: kallithea/templates/pullrequests/pullrequest_show.html:316 #, python-format msgid "Showing %s commit" msgid_plural "Showing %s commits" msgstr[0] "" -#: kallithea/templates/compare/compare_diff.html:78 -#: kallithea/templates/compare/compare_diff.html:89 +#: kallithea/templates/compare/compare_diff.html:84 +#: kallithea/templates/compare/compare_diff.html:93 msgid "Show full diff" msgstr "" @@ -5231,17 +5220,23 @@ msgid "We have received a request to reset the password for your account." msgstr "" -#: kallithea/templates/email_templates/password_reset.html:7 -msgid "To set a new password, click the following link" +#: kallithea/templates/email_templates/password_reset.html:8 +msgid "" +"This account is however managed outside this system and the password " +"cannot be changed here." msgstr "" #: kallithea/templates/email_templates/password_reset.html:10 +msgid "To set a new password, click the following link" +msgstr "" + +#: kallithea/templates/email_templates/password_reset.html:13 msgid "" "Should you not be able to use the link above, please type the following " "code into the password reset form" msgstr "" -#: kallithea/templates/email_templates/password_reset.html:12 +#: kallithea/templates/email_templates/password_reset.html:16 msgid "" "If it weren't you who requested the password reset, just disregard this " "message." @@ -5324,8 +5319,9 @@ msgstr "" #: kallithea/templates/files/files_add.html:53 -msgid "New file mode" -msgstr "" +#, fuzzy +msgid "New file type" +msgstr "未知的存檔類型" #: kallithea/templates/files/files_add.html:64 #: kallithea/templates/files/files_delete.html:43 @@ -5457,10 +5453,19 @@ msgid "Binary file (%s)" msgstr "二進位檔 (%s)" -#: kallithea/templates/files/files_source.html:73 -msgid "File is too big to display" +#: kallithea/templates/files/files_source.html:74 +#, fuzzy +msgid "File is too big to display." msgstr "顯示的檔案太大" +#: kallithea/templates/files/files_source.html:76 +msgid "Show full annotation anyway." +msgstr "" + +#: kallithea/templates/files/files_source.html:78 +msgid "Show as raw." +msgstr "" + #: kallithea/templates/files/files_ypjax.html:5 msgid "annotation" msgstr "" @@ -5725,42 +5730,48 @@ msgid "Current revision - no change" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:213 +#: kallithea/templates/pullrequests/pullrequest_show.html:215 +msgid "" +"Pull requests do not change once created. Select a revision and save to " +"replace this pull request with a new one." +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:224 msgid "Pull Request Reviewers" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:238 +#: kallithea/templates/pullrequests/pullrequest_show.html:249 #, fuzzy msgid "Remove reviewer" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:250 -msgid "Type name of reviewer to add" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:258 -#, fuzzy -msgid "Potential Reviewers" -msgstr "" - #: kallithea/templates/pullrequests/pullrequest_show.html:261 +msgid "Type name of reviewer to add" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:269 +#, fuzzy +msgid "Potential Reviewers" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:272 msgid "Click to add the repository owner as reviewer:" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:284 +#: kallithea/templates/pullrequests/pullrequest_show.html:295 msgid "Save Changes" msgstr "" -#: kallithea/templates/pullrequests/pullrequest_show.html:285 -msgid "Save as New Pull Request" -msgstr "" - -#: kallithea/templates/pullrequests/pullrequest_show.html:286 +#: kallithea/templates/pullrequests/pullrequest_show.html:296 +msgid "Save Updates as New Pull Request" +msgstr "" + +#: kallithea/templates/pullrequests/pullrequest_show.html:297 #, fuzzy msgid "Cancel Changes" msgstr "沒有修改" -#: kallithea/templates/pullrequests/pullrequest_show.html:296 +#: kallithea/templates/pullrequests/pullrequest_show.html:307 #, fuzzy msgid "Pull Request Content" msgstr "文件內容" @@ -5771,9 +5782,9 @@ msgstr "" #: kallithea/templates/pullrequests/pullrequest_show_all.html:11 -#, python-format -msgid "Pull Requests from %s'" -msgstr "" +#, fuzzy, python-format +msgid "Pull Requests from '%s'" +msgstr "文件內容" #: kallithea/templates/pullrequests/pullrequest_show_all.html:13 #, python-format @@ -6325,27 +6336,6 @@ #~ msgid "with subrepos" #~ msgstr "" -#~ msgid "" -#~ "Your password reset was successful, new" -#~ " password has been sent to your " -#~ "email" -#~ msgstr "您的密碼重設動作已完成,新的密碼已寄至您的信箱" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " changeset %(short_id)s on %(branch)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Added by %(pr_username)s] %(repo_name)s pull" -#~ " request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - -#~ msgid "" -#~ "[Comment from %(comment_username)s] %(repo_name)s" -#~ " pull request %(pr_nice_id)s from %(ref)s" -#~ msgstr "" - #~ msgid "Your new password" #~ msgstr "" @@ -6373,3 +6363,47 @@ #~ msgid "Created by" #~ msgstr "" +#~ msgid "You can only delete files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "You can only edit files with revision being a valid branch " +#~ msgstr "" + +#~ msgid "This pull request can be updated with changes on %s:" +#~ msgstr "" + +#~ msgid "Confirm to invalidate repository cache." +#~ msgstr "確認廢止版本庫快取" + +#~ msgid "Commenting on line {1}." +#~ msgstr "" + +#~ msgid "Comments parsed using %s syntax with %s support." +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user" +#~ msgstr "" + +#~ msgid "Comment preview" +#~ msgstr "" + +#~ msgid "Preview" +#~ msgstr "" + +#~ msgid "Use @username inside this text to notify another user." +#~ msgstr "" + +#~ msgid "New file mode" +#~ msgstr "" + +#~ msgid "Save as New Pull Request" +#~ msgstr "" + +#~ msgid "Pull Requests from %s'" +#~ msgstr "" + +#~ msgid "" +#~ "Changeset status: %s\n" +#~ "Click to open associated pull request %s" +#~ msgstr "" + diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/__init__.py --- a/kallithea/lib/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -24,30 +24,3 @@ :copyright: (c) 2013 RhodeCode GmbH, and others. :license: GPLv3, see LICENSE.md for more details. """ - -import os - -def get_current_revision(quiet=False): - """ - Returns tuple of (number, id) from repository containing this package - or None if repository could not be found. - - :param quiet: prints error for fetching revision if True - """ - - try: - from kallithea.lib.vcs import get_repo - from kallithea.lib.vcs.utils.helpers import get_scm - repopath = os.path.abspath(os.path.join(os.path.dirname(__file__), - '..', '..')) - scm = get_scm(repopath)[0] - repo = get_repo(path=repopath, alias=scm) - wk_dir = repo.workdir - cur_rev = wk_dir.get_changeset() - return (cur_rev.revision, cur_rev.short_id) - except Exception as err: - if not quiet: - print ("WARNING: Cannot retrieve kallithea's revision. " - "disregard this if you don't know what that means. " - "Original error was: %s" % err) - return None diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/annotate.py --- a/kallithea/lib/annotate.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/annotate.py Sat Dec 24 00:34:38 2016 +0100 @@ -48,7 +48,7 @@ :param headers: dictionary with headers (keys are whats in ``order`` parameter) """ - from kallithea.lib.utils import get_custom_lexer + from kallithea.lib.pygmentsutils import get_custom_lexer options['linenos'] = True formatter = AnnotateHtmlFormatter(filenode=filenode, order=order, headers=headers, @@ -68,7 +68,7 @@ following function as ``annotate_from_changeset_func``:: def changeset_to_anchor(changeset): - return '%s\n' %\ + return '%s\n' % \ (changeset.id, changeset.id) :param annotate_from_changeset_func: see above diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/auth.py --- a/kallithea/lib/auth.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/auth.py Sat Dec 24 00:34:38 2016 +0100 @@ -34,15 +34,16 @@ from decorator import decorator -from pylons import url, request, session -from pylons.controllers.util import abort, redirect +from pylons import request, session from pylons.i18n.translation import _ from webhelpers.pylonslib import secure_form from sqlalchemy import or_ from sqlalchemy.orm.exc import ObjectDeletedError from sqlalchemy.orm import joinedload +from webob.exc import HTTPFound, HTTPBadRequest, HTTPForbidden, HTTPMethodNotAllowed from kallithea import __platform__, is_windows, is_unix +from kallithea.config.routing import url from kallithea.lib.vcs.utils.lazy import LazyProperty from kallithea.model import meta from kallithea.model.meta import Session @@ -52,7 +53,7 @@ RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \ UserGroup, UserApiKeys -from kallithea.lib.utils2 import safe_unicode, aslist +from kallithea.lib.utils2 import safe_str, safe_unicode, aslist from kallithea.lib.utils import get_repo_slug, get_repo_group_slug, \ get_user_group_slug, conditional_cache from kallithea.lib.caching_query import FromCache @@ -93,52 +94,40 @@ return ''.join(l) -class KallitheaCrypto(object): - - @classmethod - def hash_string(cls, str_): - """ - Cryptographic function used for password hashing based on pybcrypt - or Python's own OpenSSL wrapper on windows - - :param password: password to hash - """ - if is_windows: - return hashlib.sha256(str_).hexdigest() - elif is_unix: - import bcrypt - return bcrypt.hashpw(str_, bcrypt.gensalt(10)) - else: - raise Exception('Unknown or unsupported platform %s' \ - % __platform__) +def get_crypt_password(password): + """ + Cryptographic function used for password hashing based on pybcrypt + or Python's own OpenSSL wrapper on windows - @classmethod - def hash_check(cls, password, hashed): - """ - Checks matching password with it's hashed value, runs different - implementation based on platform it runs on - - :param password: password - :param hashed: password in hashed form - """ - - if is_windows: - return hashlib.sha256(password).hexdigest() == hashed - elif is_unix: - import bcrypt - return bcrypt.hashpw(password, hashed) == hashed - else: - raise Exception('Unknown or unsupported platform %s' \ - % __platform__) - - -def get_crypt_password(password): - return KallitheaCrypto.hash_string(password) + :param password: password to hash + """ + if is_windows: + return hashlib.sha256(password).hexdigest() + elif is_unix: + import bcrypt + return bcrypt.hashpw(safe_str(password), bcrypt.gensalt(10)) + else: + raise Exception('Unknown or unsupported platform %s' \ + % __platform__) def check_password(password, hashed): - return KallitheaCrypto.hash_check(password, hashed) + """ + Checks matching password with it's hashed value, runs different + implementation based on platform it runs on + + :param password: password + :param hashed: password in hashed form + """ + if is_windows: + return hashlib.sha256(password).hexdigest() == hashed + elif is_unix: + import bcrypt + return bcrypt.checkpw(safe_str(password), safe_str(hashed)) + else: + raise Exception('Unknown or unsupported platform %s' \ + % __platform__) def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions, @@ -204,8 +193,8 @@ #================================================================== # default global permissions taken from the default user - default_global_perms = UserToPerm.query()\ - .filter(UserToPerm.user_id == default_user_id)\ + default_global_perms = UserToPerm.query() \ + .filter(UserToPerm.user_id == default_user_id) \ .options(joinedload(UserToPerm.permission)) for perm in default_global_perms: @@ -214,10 +203,10 @@ # defaults for repositories, taken from default user for perm in default_repo_perms: r_k = perm.UserRepoToPerm.repository.repo_name - if perm.Repository.private and not (perm.Repository.user_id == user_id): + if perm.Repository.private and not (perm.Repository.owner_id == user_id): # disable defaults for private repos, p = 'repository.none' - elif perm.Repository.user_id == user_id: + elif perm.Repository.owner_id == user_id: # set admin if owner p = 'repository.admin' else: @@ -251,15 +240,15 @@ # USER GROUPS comes first # user group global permissions - user_perms_from_users_groups = Session().query(UserGroupToPerm)\ - .options(joinedload(UserGroupToPerm.permission))\ + user_perms_from_users_groups = Session().query(UserGroupToPerm) \ + .options(joinedload(UserGroupToPerm.permission)) \ .join((UserGroupMember, UserGroupToPerm.users_group_id == - UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == user_id)\ + UserGroupMember.users_group_id)) \ + .filter(UserGroupMember.user_id == user_id) \ .join((UserGroup, UserGroupMember.users_group_id == - UserGroup.users_group_id))\ - .filter(UserGroup.users_group_active == True)\ - .order_by(UserGroupToPerm.users_group_id)\ + UserGroup.users_group_id)) \ + .filter(UserGroup.users_group_active == True) \ + .order_by(UserGroupToPerm.users_group_id) \ .all() # need to group here by groups since user can be in more than # one group @@ -273,20 +262,20 @@ if not gr.inherit_default_permissions: # NEED TO IGNORE all configurable permissions and # replace them with explicitly set - permissions[GLOBAL] = permissions[GLOBAL]\ + permissions[GLOBAL] = permissions[GLOBAL] \ .difference(_configurable) for perm in perms: permissions[GLOBAL].add(perm.permission.permission_name) # user specific global permissions - user_perms = Session().query(UserToPerm)\ - .options(joinedload(UserToPerm.permission))\ + user_perms = Session().query(UserToPerm) \ + .options(joinedload(UserToPerm.permission)) \ .filter(UserToPerm.user_id == user_id).all() if not user_inherit_default_permissions: # NEED TO IGNORE all configurable permissions and # replace them with explicitly set - permissions[GLOBAL] = permissions[GLOBAL]\ + permissions[GLOBAL] = permissions[GLOBAL] \ .difference(_configurable) for perm in user_perms: @@ -304,17 +293,17 @@ # user group for repositories permissions user_repo_perms_from_users_groups = \ - Session().query(UserGroupRepoToPerm, Permission, Repository,)\ + Session().query(UserGroupRepoToPerm, Permission, Repository,) \ .join((Repository, UserGroupRepoToPerm.repository_id == - Repository.repo_id))\ + Repository.repo_id)) \ .join((Permission, UserGroupRepoToPerm.permission_id == - Permission.permission_id))\ + Permission.permission_id)) \ .join((UserGroup, UserGroupRepoToPerm.users_group_id == - UserGroup.users_group_id))\ - .filter(UserGroup.users_group_active == True)\ + UserGroup.users_group_id)) \ + .filter(UserGroup.users_group_active == True) \ .join((UserGroupMember, UserGroupRepoToPerm.users_group_id == - UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == user_id)\ + UserGroupMember.users_group_id)) \ + .filter(UserGroupMember.user_id == user_id) \ .all() multiple_counter = collections.defaultdict(int) @@ -324,7 +313,7 @@ p = perm.Permission.permission_name cur_perm = permissions[RK][r_k] - if perm.Repository.user_id == user_id: + if perm.Repository.owner_id == user_id: # set admin if owner p = 'repository.admin' else: @@ -339,7 +328,7 @@ r_k = perm.UserRepoToPerm.repository.repo_name cur_perm = permissions[RK][r_k] # set admin if owner - if perm.Repository.user_id == user_id: + if perm.Repository.owner_id == user_id: p = 'repository.admin' else: p = perm.Permission.permission_name @@ -357,16 +346,16 @@ #====================================================================== # user group for repo groups permissions user_repo_group_perms_from_users_groups = \ - Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\ - .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\ + Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup) \ + .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)) \ .join((Permission, UserGroupRepoGroupToPerm.permission_id - == Permission.permission_id))\ + == Permission.permission_id)) \ .join((UserGroup, UserGroupRepoGroupToPerm.users_group_id == - UserGroup.users_group_id))\ - .filter(UserGroup.users_group_active == True)\ + UserGroup.users_group_id)) \ + .filter(UserGroup.users_group_active == True) \ .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id - == UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == user_id)\ + == UserGroupMember.users_group_id)) \ + .filter(UserGroupMember.user_id == user_id) \ .all() multiple_counter = collections.defaultdict(int) @@ -394,17 +383,17 @@ #====================================================================== # user group for user group permissions user_group_user_groups_perms = \ - Session().query(UserGroupUserGroupToPerm, Permission, UserGroup)\ + Session().query(UserGroupUserGroupToPerm, Permission, UserGroup) \ .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id - == UserGroup.users_group_id))\ + == UserGroup.users_group_id)) \ .join((Permission, UserGroupUserGroupToPerm.permission_id - == Permission.permission_id))\ + == Permission.permission_id)) \ .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id - == UserGroupMember.users_group_id))\ - .filter(UserGroupMember.user_id == user_id)\ + == UserGroupMember.users_group_id)) \ + .filter(UserGroupMember.user_id == user_id) \ .join((UserGroup, UserGroupMember.users_group_id == - UserGroup.users_group_id), aliased=True, from_joinpoint=True)\ - .filter(UserGroup.users_group_active == True)\ + UserGroup.users_group_id), aliased=True, from_joinpoint=True) \ + .filter(UserGroup.users_group_active == True) \ .all() multiple_counter = collections.defaultdict(int) @@ -465,8 +454,7 @@ access to Kallithea is enabled, the default user is loaded instead. `AuthUser` does not by itself authenticate users and the constructor - sets the `is_authenticated` field to False, except when falling back - to the default anonymous user (if enabled). It's up to other parts + sets the `is_authenticated` field to False. It's up to other parts of the code to check e.g. if a supplied password is correct, and if so, set `is_authenticated` to True. @@ -482,7 +470,7 @@ user_model = UserModel() self.anonymous_user = User.get_default_user(cache=True) - # These attributes will be overriden by fill_data, below, unless the + # These attributes will be overridden by fill_data, below, unless the # requested user cannot be found and the default anonymous user is # not enabled. self.user_id = None @@ -508,9 +496,7 @@ if not is_user_loaded: is_user_loaded = self._fill_data(self.anonymous_user) - # The anonymous user is always "logged in". - if self.user_id == self.anonymous_user.user_id: - self.is_authenticated = True + self.is_default_user = (self.user_id == self.anonymous_user.user_id) if not self.username: self.username = 'None' @@ -569,8 +555,8 @@ def _get_api_keys(self): api_keys = [self.api_key] - for api_key in UserApiKeys.query()\ - .filter(UserApiKeys.user_id == self.user_id)\ + for api_key in UserApiKeys.query() \ + .filter(UserApiKeys.user_id == self.user_id) \ .filter(or_(UserApiKeys.expires == -1, UserApiKeys.expires >= time.time())).all(): api_keys.append(api_key.api_key) @@ -622,18 +608,13 @@ return False def __repr__(self): - return ""\ - % (self.user_id, self.username, self.is_authenticated) - - def set_authenticated(self, authenticated=True): - if self.user_id != self.anonymous_user.user_id: - self.is_authenticated = authenticated + return "" \ + % (self.user_id, self.username, (self.is_authenticated or self.is_default_user)) def to_cookie(self): """ Serializes this login session to a cookie `dict`. """ return { 'user_id': self.user_id, - 'is_authenticated': self.is_authenticated, 'is_external_auth': self.is_external_auth, } @@ -647,9 +628,7 @@ user_id=cookie.get('user_id'), is_external_auth=cookie.get('is_external_auth', False), ) - if not au.is_authenticated and au.user_id is not None: - # user is not authenticated and not empty - au.set_authenticated(cookie.get('is_authenticated')) + au.is_authenticated = True return au @classmethod @@ -689,12 +668,12 @@ def set_available_permissions(config): """ - This function will propagate pylons globals with all available defined + This function will propagate globals with all available defined permission given in db. We don't want to check each time from db for new permissions since adding a new permission also requires application restart ie. to decorate new views with the newly created permission - :param config: current pylons config instance + :param config: current config instance """ log.info('getting information about all available permissions') @@ -710,13 +689,16 @@ # CHECK DECORATORS #============================================================================== -def redirect_to_login(message=None): +def _redirect_to_login(message=None): + """Return an exception that must be raised. It will redirect to the login + page which will redirect back to the current URL after authentication. + The optional message will be shown in a flash message.""" from kallithea.lib import helpers as h - p = request.path_qs if message: h.flash(h.literal(message), category='warning') + p = request.path_qs log.debug('Redirecting to login page, origin: %s', p) - return redirect(url('login_home', came_from=p)) + return HTTPFound(location=url('login_home', came_from=p)) class LoginRequired(object): @@ -741,7 +723,7 @@ log.debug('Checking access for user %s @ %s', user, loc) if not AuthUser.check_ip_allowed(user, controller.ip_addr): - return redirect_to_login(_('IP %s not allowed') % controller.ip_addr) + raise _redirect_to_login(_('IP %s not allowed') % controller.ip_addr) # check if we used an API key and it's a valid one api_key = request.GET.get('api_key') @@ -754,25 +736,20 @@ return func(*fargs, **fkwargs) else: log.warning('API key ****%s is NOT valid', api_key[-4:]) - return redirect_to_login(_('Invalid API key')) + raise _redirect_to_login(_('Invalid API key')) else: # controller does not allow API access log.warning('API access to %s is not allowed', loc) - return abort(403) + raise HTTPForbidden() - # Only allow the following HTTP request methods. (We sometimes use POST - # requests with a '_method' set to 'PUT' or 'DELETE'; but that is only - # used for the route lookup, and does not affect request.method.) - if request.method not in ['GET', 'HEAD', 'POST', 'PUT']: - return abort(405) + # Only allow the following HTTP request methods. + if request.method not in ['GET', 'HEAD', 'POST']: + raise HTTPMethodNotAllowed() - # Also verify the _method override. This is only permitted in POST - # requests, and can specify PUT or DELETE. + # Also verify the _method override - no longer allowed _method = request.params.get('_method') if _method is None: pass # no override, no problem - elif request.method == 'POST' and _method.upper() in ['PUT', 'DELETE']: - pass # permitted override else: raise HTTPMethodNotAllowed() @@ -795,22 +772,22 @@ token = request.POST.get(secure_form.token_key) if not token or token != secure_form.authentication_token(): log.error('CSRF check failed') - return abort(403) + raise HTTPForbidden() # WebOb already ignores request payload parameters for anything other # than POST/PUT, but double-check since other Kallithea code relies on # this assumption. if request.method not in ['POST', 'PUT'] and request.POST: log.error('%r request with payload parameters; WebOb should have stopped this', request.method) - return abort(400) + raise HTTPBadRequest() # regular user authentication - if user.is_authenticated: + if user.is_authenticated or user.is_default_user: log.info('user %s authenticated with regular auth @ %s', user, loc) return func(*fargs, **fkwargs) else: log.warning('user %s NOT authenticated with regular auth @ %s', user, loc) - return redirect_to_login() + raise _redirect_to_login() class NotAnonymous(object): """ @@ -826,11 +803,9 @@ log.debug('Checking if user is not anonymous @%s', cls) - anonymous = self.user.username == User.DEFAULT_USER - - if anonymous: - return redirect_to_login(_('You need to be a registered user to ' - 'perform this action')) + if self.user.is_default_user: + raise _redirect_to_login(_('You need to be a registered user to ' + 'perform this action')) else: return func(*fargs, **fkwargs) @@ -858,31 +833,16 @@ else: log.debug('Permission denied for %s %s', cls, self.user) - anonymous = self.user.username == User.DEFAULT_USER - - if anonymous: - return redirect_to_login(_('You need to be signed in to view this page')) + if self.user.is_default_user: + raise _redirect_to_login(_('You need to be signed in to view this page')) else: - # redirect with forbidden ret code - return abort(403) + raise HTTPForbidden() def check_permissions(self): """Dummy function for overriding""" raise Exception('You have to write this function in child class') -class HasPermissionAllDecorator(PermsDecorator): - """ - Checks for access permission for all given predicates. All of them - have to be meet in order to fulfill the request - """ - - def check_permissions(self): - if self.required_perms.issubset(self.user_perms.get('global')): - return True - return False - - class HasPermissionAnyDecorator(PermsDecorator): """ Checks for access permission for any of given predicates. In order to @@ -895,23 +855,6 @@ return False -class HasRepoPermissionAllDecorator(PermsDecorator): - """ - Checks for access permission for all given predicates for specific - repository. All of them have to be meet in order to fulfill the request - """ - - def check_permissions(self): - repo_name = get_repo_slug(request) - try: - user_perms = set([self.user_perms['repositories'][repo_name]]) - except KeyError: - return False - if self.required_perms.issubset(user_perms): - return True - return False - - class HasRepoPermissionAnyDecorator(PermsDecorator): """ Checks for access permission for any of given predicates for specific @@ -930,24 +873,6 @@ return False -class HasRepoGroupPermissionAllDecorator(PermsDecorator): - """ - Checks for access permission for all given predicates for specific - repository group. All of them have to be meet in order to fulfill the request - """ - - def check_permissions(self): - group_name = get_repo_group_slug(request) - try: - user_perms = set([self.user_perms['repositories_groups'][group_name]]) - except KeyError: - return False - - if self.required_perms.issubset(user_perms): - return True - return False - - class HasRepoGroupPermissionAnyDecorator(PermsDecorator): """ Checks for access permission for any of given predicates for specific @@ -966,24 +891,6 @@ return False -class HasUserGroupPermissionAllDecorator(PermsDecorator): - """ - Checks for access permission for all given predicates for specific - user group. All of them have to be meet in order to fulfill the request - """ - - def check_permissions(self): - group_name = get_user_group_slug(request) - try: - user_perms = set([self.user_perms['user_groups'][group_name]]) - except KeyError: - return False - - if self.required_perms.issubset(user_perms): - return True - return False - - class HasUserGroupPermissionAnyDecorator(PermsDecorator): """ Checks for access permission for any of given predicates for specific @@ -1014,53 +921,37 @@ self.repo_name = None self.group_name = None - def __call__(self, check_location='', user=None): - if not user: - #TODO: remove this someday,put as user as attribute here - user = request.user + def __nonzero__(self): + """ Defend against accidentally forgetting to call the object + and instead evaluating it directly in a boolean context, + which could have security implications. + """ + raise AssertionError(self.__class__.__name__ + ' is not a bool and must be called!') - # init auth user if not already given - if not isinstance(user, AuthUser): - user = AuthUser(user.user_id) + def __call__(self, check_location='unspecified location'): + user = request.user + assert user + assert isinstance(user, AuthUser), user cls_name = self.__class__.__name__ - check_scope = { - 'HasPermissionAll': '', - 'HasPermissionAny': '', - 'HasRepoPermissionAll': 'repo:%s' % self.repo_name, - 'HasRepoPermissionAny': 'repo:%s' % self.repo_name, - 'HasRepoGroupPermissionAll': 'group:%s' % self.group_name, - 'HasRepoGroupPermissionAny': 'group:%s' % self.group_name, - }.get(cls_name, '?') + check_scope = self._scope() log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name, self.required_perms, user, check_scope, - check_location or 'unspecified location') - if not user: - log.debug('Empty request user') - return False + check_location) self.user_perms = user.permissions - if self.check_permissions(): - log.debug('Permission to %s granted for user: %s @ %s', - check_scope, user, - check_location or 'unspecified location') - return True - else: - log.debug('Permission to %s denied for user: %s @ %s', - check_scope, user, - check_location or 'unspecified location') - return False + result = self.check_permissions() + result_text = 'granted' if result else 'denied' + log.debug('Permission to %s %s for user: %s @ %s', + check_scope, result_text, user, check_location) + return result def check_permissions(self): """Dummy function for overriding""" raise Exception('You have to write this function in child class') - -class HasPermissionAll(PermsFunction): - def check_permissions(self): - if self.required_perms.issubset(self.user_perms.get('global')): - return True - return False + def _scope(self): + return '(unknown scope)' class HasPermissionAny(PermsFunction): @@ -1069,31 +960,14 @@ return True return False - -class HasRepoPermissionAll(PermsFunction): - def __call__(self, repo_name=None, check_location='', user=None): - self.repo_name = repo_name - return super(HasRepoPermissionAll, self).__call__(check_location, user) - - def check_permissions(self): - if not self.repo_name: - self.repo_name = get_repo_slug(request) - - try: - self._user_perms = set( - [self.user_perms['repositories'][self.repo_name]] - ) - except KeyError: - return False - if self.required_perms.issubset(self._user_perms): - return True - return False + def _scope(self): + return 'global' class HasRepoPermissionAny(PermsFunction): - def __call__(self, repo_name=None, check_location='', user=None): + def __call__(self, repo_name=None, check_location=''): self.repo_name = repo_name - return super(HasRepoPermissionAny, self).__call__(check_location, user) + return super(HasRepoPermissionAny, self).__call__(check_location) def check_permissions(self): if not self.repo_name: @@ -1109,11 +983,14 @@ return True return False + def _scope(self): + return 'repo:%s' % self.repo_name + class HasRepoGroupPermissionAny(PermsFunction): - def __call__(self, group_name=None, check_location='', user=None): + def __call__(self, group_name=None, check_location=''): self.group_name = group_name - return super(HasRepoGroupPermissionAny, self).__call__(check_location, user) + return super(HasRepoGroupPermissionAny, self).__call__(check_location) def check_permissions(self): try: @@ -1126,28 +1003,14 @@ return True return False - -class HasRepoGroupPermissionAll(PermsFunction): - def __call__(self, group_name=None, check_location='', user=None): - self.group_name = group_name - return super(HasRepoGroupPermissionAll, self).__call__(check_location, user) - - def check_permissions(self): - try: - self._user_perms = set( - [self.user_perms['repositories_groups'][self.group_name]] - ) - except KeyError: - return False - if self.required_perms.issubset(self._user_perms): - return True - return False + def _scope(self): + return 'repogroup:%s' % self.group_name class HasUserGroupPermissionAny(PermsFunction): - def __call__(self, user_group_name=None, check_location='', user=None): + def __call__(self, user_group_name=None, check_location=''): self.user_group_name = user_group_name - return super(HasUserGroupPermissionAny, self).__call__(check_location, user) + return super(HasUserGroupPermissionAny, self).__call__(check_location) def check_permissions(self): try: @@ -1160,22 +1023,8 @@ return True return False - -class HasUserGroupPermissionAll(PermsFunction): - def __call__(self, user_group_name=None, check_location='', user=None): - self.user_group_name = user_group_name - return super(HasUserGroupPermissionAll, self).__call__(check_location, user) - - def check_permissions(self): - try: - self._user_perms = set( - [self.user_perms['user_groups'][self.user_group_name]] - ) - except KeyError: - return False - if self.required_perms.issubset(self._user_perms): - return True - return False + def _scope(self): + return 'usergroup:%s' % self.user_group_name #============================================================================== @@ -1208,115 +1057,6 @@ return False -#============================================================================== -# SPECIAL VERSION TO HANDLE API AUTH -#============================================================================== -class _BaseApiPerm(object): - def __init__(self, *perms): - self.required_perms = set(perms) - - def __call__(self, check_location=None, user=None, repo_name=None, - group_name=None): - cls_name = self.__class__.__name__ - check_scope = 'user:%s' % (user) - if repo_name: - check_scope += ', repo:%s' % (repo_name) - - if group_name: - check_scope += ', repo group:%s' % (group_name) - - log.debug('checking cls:%s %s %s @ %s', - cls_name, self.required_perms, check_scope, check_location) - if not user: - log.debug('Empty User passed into arguments') - return False - - ## process user - if not isinstance(user, AuthUser): - user = AuthUser(user.user_id) - if not check_location: - check_location = 'unspecified' - if self.check_permissions(user.permissions, repo_name, group_name): - log.debug('Permission to %s granted for user: %s @ %s', - check_scope, user, check_location) - return True - - else: - log.debug('Permission to %s denied for user: %s @ %s', - check_scope, user, check_location) - return False - - def check_permissions(self, perm_defs, repo_name=None, group_name=None): - """ - implement in child class should return True if permissions are ok, - False otherwise - - :param perm_defs: dict with permission definitions - :param repo_name: repo name - """ - raise NotImplementedError() - - -class HasPermissionAllApi(_BaseApiPerm): - def check_permissions(self, perm_defs, repo_name=None, group_name=None): - if self.required_perms.issubset(perm_defs.get('global')): - return True - return False - - -class HasPermissionAnyApi(_BaseApiPerm): - def check_permissions(self, perm_defs, repo_name=None, group_name=None): - if self.required_perms.intersection(perm_defs.get('global')): - return True - return False - - -class HasRepoPermissionAllApi(_BaseApiPerm): - def check_permissions(self, perm_defs, repo_name=None, group_name=None): - try: - _user_perms = set([perm_defs['repositories'][repo_name]]) - except KeyError: - log.warning(traceback.format_exc()) - return False - if self.required_perms.issubset(_user_perms): - return True - return False - - -class HasRepoPermissionAnyApi(_BaseApiPerm): - def check_permissions(self, perm_defs, repo_name=None, group_name=None): - try: - _user_perms = set([perm_defs['repositories'][repo_name]]) - except KeyError: - log.warning(traceback.format_exc()) - return False - if self.required_perms.intersection(_user_perms): - return True - return False - - -class HasRepoGroupPermissionAnyApi(_BaseApiPerm): - def check_permissions(self, perm_defs, repo_name=None, group_name=None): - try: - _user_perms = set([perm_defs['repositories_groups'][group_name]]) - except KeyError: - log.warning(traceback.format_exc()) - return False - if self.required_perms.intersection(_user_perms): - return True - return False - -class HasRepoGroupPermissionAllApi(_BaseApiPerm): - def check_permissions(self, perm_defs, repo_name=None, group_name=None): - try: - _user_perms = set([perm_defs['repositories_groups'][group_name]]) - except KeyError: - log.warning(traceback.format_exc()) - return False - if self.required_perms.issubset(_user_perms): - return True - return False - def check_ip_access(source_ip, allowed_ips=None): """ Checks if source_ip is a subnet of any of allowed_ips. diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/auth_modules/__init__.py --- a/kallithea/lib/auth_modules/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/auth_modules/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -17,9 +17,8 @@ import logging import traceback +import importlib -from kallithea import EXTERN_TYPE_INTERNAL -from kallithea.lib.compat import importlib from kallithea.lib.utils2 import str2bool from kallithea.lib.compat import formatted_json, hybrid_property from kallithea.lib.auth import PasswordGenerator @@ -139,8 +138,8 @@ log.debug('Trying to fetch user `%s` from Kallithea database', username) if username: - user = User.get_by_username(username) - if not user: + user = User.get_by_username_or_email(username) + if user is None: log.debug('Fallback to fetch user in case insensitive mode') user = User.get_by_username(username, case_insensitive=True) else: @@ -289,7 +288,6 @@ extern_name=user_data["extern_name"], extern_type=self.name ) - Session().flush() # enforce user is just in given groups, all of them has to be ones # created from plugins. We store this info in _group_data JSON field groups = user_data['groups'] or [] @@ -314,8 +312,6 @@ parts = plugin.split(u'.lib.auth_modules.auth_', 1) if len(parts) == 2: _module, pn = parts - if pn == EXTERN_TYPE_INTERNAL: - pn = "internal" plugin = u'kallithea.lib.auth_modules.auth_' + pn PLUGIN_CLASS_NAME = "KallitheaAuthPlugin" try: @@ -395,8 +391,15 @@ else: log.debug('Plugin %s accepted user `%s` for authentication', module, user) + # The user might have tried to authenticate using their email address, + # then the username variable wouldn't contain a valid username. + # But as the plugin has accepted the user, .username field should + # have a valid username, so use it for authentication purposes. + if user is not None: + username = user.username log.info('Authenticating user using %s plugin', plugin.__module__) + # _authenticate is a wrapper for .auth() method of plugin. # it checks if .auth() sends proper data. For KallitheaExternalAuthPlugin # it also maps users to Database and maps the attributes returned diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/auth_modules/auth_container.py --- a/kallithea/lib/auth_modules/auth_container.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/auth_modules/auth_container.py Sat Dec 24 00:34:38 2016 +0100 @@ -27,9 +27,9 @@ import logging from kallithea.lib import auth_modules -from kallithea.lib.utils2 import str2bool, safe_unicode +from kallithea.lib.utils2 import str2bool, safe_unicode, safe_str from kallithea.lib.compat import hybrid_property -from kallithea.model.db import User +from kallithea.model.db import User, Setting log = logging.getLogger(__name__) @@ -53,15 +53,39 @@ "name": "header", "validator": self.validators.UnicodeString(strip=True, not_empty=True), "type": "string", - "description": "Header to extract the user from", + "description": "Request header to extract the username from", "default": "REMOTE_USER", - "formname": "Header" + "formname": "Username header" + }, + { + "name": "email_header", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Optional request header to extract the email from", + "default": "", + "formname": "Email header" + }, + { + "name": "firstname_header", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Optional request header to extract the first name from", + "default": "", + "formname": "Firstname header" + }, + { + "name": "lastname_header", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Optional request header to extract the last name from", + "default": "", + "formname": "Lastname header" }, { "name": "fallback_header", "validator": self.validators.UnicodeString(strip=True), "type": "string", - "description": "Header to extract the user from when main one fails", + "description": "Request header to extract the user from when main one fails", "default": "HTTP_X_FORWARDED_USER", "formname": "Fallback header" }, @@ -158,7 +182,7 @@ # only way to log in is using environ username = None if userobj: - username = getattr(userobj, 'username') + username = safe_str(getattr(userobj, 'username')) if not username: # we don't have any objects in DB, user doesn't exist, extract @@ -172,9 +196,9 @@ # old attrs fetched from Kallithea database admin = getattr(userobj, 'admin', False) active = getattr(userobj, 'active', True) - email = getattr(userobj, 'email', '') - firstname = getattr(userobj, 'firstname', '') - lastname = getattr(userobj, 'lastname', '') + email = environ.get(settings.get('email_header'), getattr(userobj, 'email', '')) + firstname = environ.get(settings.get('firstname_header'), getattr(userobj, 'firstname', '')) + lastname = environ.get(settings.get('lastname_header'), getattr(userobj, 'lastname', '')) user_data = { 'username': username, @@ -192,4 +216,11 @@ return user_data def get_managed_fields(self): - return ['username', 'password'] + fields = ['username', 'password'] + if(Setting.get_by_name('auth_container_email_header').app_settings_value): + fields.append('email') + if(Setting.get_by_name('auth_container_firstname_header').app_settings_value): + fields.append('firstname') + if(Setting.get_by_name('auth_container_lastname_header').app_settings_value): + fields.append('lastname') + return fields diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/auth_modules/auth_internal.py --- a/kallithea/lib/auth_modules/auth_internal.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/auth_modules/auth_internal.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,7 +28,6 @@ import logging -from kallithea import EXTERN_TYPE_INTERNAL from kallithea.lib import auth_modules from kallithea.lib.compat import formatted_json, hybrid_property from kallithea.model.db import User @@ -42,7 +41,8 @@ @hybrid_property def name(self): - return EXTERN_TYPE_INTERNAL + # Also found as kallithea.lib.model.db.User.DEFAULT_AUTH_TYPE + return 'internal' def settings(self): return [] @@ -86,7 +86,7 @@ log.debug(formatted_json(user_data)) if userobj.active: from kallithea.lib import auth - password_match = auth.KallitheaCrypto.hash_check(password, userobj.password) + password_match = auth.check_password(password, userobj.password) if userobj.username == User.DEFAULT_USER and userobj.active: log.info('user %s authenticated correctly as anonymous user', username) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/auth_modules/auth_ldap.py --- a/kallithea/lib/auth_modules/auth_ldap.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/auth_modules/auth_ldap.py Sat Dec 24 00:34:38 2016 +0100 @@ -41,6 +41,7 @@ try: import ldap + import ldap.filter except ImportError: # means that python-ldap is not installed ldap = None @@ -48,38 +49,33 @@ class AuthLdap(object): - def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', - tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, + def __init__(self, server, base_dn, port=None, bind_dn='', bind_pass='', + tls_kind='PLAIN', tls_reqcert='DEMAND', cacertdir=None, ldap_version=3, ldap_filter='(&(objectClass=user)(!(objectClass=computer)))', search_scope='SUBTREE', attr_login='uid'): if ldap is None: raise LdapImportError self.ldap_version = ldap_version - ldap_server_type = 'ldap' self.TLS_KIND = tls_kind - - if self.TLS_KIND == 'LDAPS': - port = port or 689 - ldap_server_type = ldap_server_type + 's' - OPT_X_TLS_DEMAND = 2 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, OPT_X_TLS_DEMAND) - # split server into list - self.LDAP_SERVER_ADDRESS = server.split(',') - self.LDAP_SERVER_PORT = port + self.cacertdir = cacertdir - # USE FOR READ ONLY BIND TO LDAP SERVER + protocol = 'ldaps' if self.TLS_KIND == 'LDAPS' else 'ldap' + if not port: + port = 636 if self.TLS_KIND == 'LDAPS' else 389 + self.LDAP_SERVER = str(', '.join( + "%s://%s:%s" % (protocol, + host.strip(), + port) + for host in server.split(','))) + self.LDAP_BIND_DN = safe_str(bind_dn) self.LDAP_BIND_PASS = safe_str(bind_pass) - _LDAP_SERVERS = [] - for host in self.LDAP_SERVER_ADDRESS: - _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type, - host.replace(' ', ''), - self.LDAP_SERVER_PORT)) - self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS)) + self.BASE_DN = safe_str(base_dn) self.LDAP_FILTER = safe_str(ldap_filter) self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) @@ -96,10 +92,6 @@ :param password: password """ - from kallithea.lib.helpers import chop_at - - uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS) - if not password: log.debug("Attempt to authenticate LDAP user " "with blank password rejected.") @@ -107,9 +99,11 @@ if "," in username: raise LdapUsernameError("invalid character in username: ,") try: - if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, - '/etc/openldap/cacerts') + if self.cacertdir: + if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.cacertdir) + else: + log.debug("OPT_X_TLS_CACERTDIR is not available - can't set %s", self.cacertdir) ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) ldap.set_option(ldap.OPT_TIMEOUT, 20) @@ -131,8 +125,9 @@ self.LDAP_BIND_DN) server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) - filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, - username) + filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, + ldap.filter.escape_filter_chars(self.attr_login), + ldap.filter.escape_filter_chars(username)) log.debug("Authenticating %r filter %s at %s", self.BASE_DN, filter_, self.LDAP_SERVER) lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, @@ -148,26 +143,28 @@ try: log.debug('Trying simple bind with %s', dn) server.simple_bind_s(dn, safe_str(password)) - attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, - '(objectClass=*)')[0][1] - break + results = server.search_ext_s(dn, ldap.SCOPE_BASE, + '(objectClass=*)') + if len(results) == 1: + dn_, attrs = results[0] + assert dn_ == dn + return dn, attrs except ldap.INVALID_CREDENTIALS: - log.debug("LDAP rejected password for user '%s' (%s): %s", - uid, username, dn) + log.debug("LDAP rejected password for user '%s': %s", + username, dn) + continue # accept authentication as another ldap user with same username - else: - log.debug("No matching LDAP objects for authentication " - "of '%s' (%s)", uid, username) - raise LdapPasswordError() + log.debug("No matching LDAP objects for authentication " + "of '%s'", username) + raise LdapPasswordError() except ldap.NO_SUCH_OBJECT: - log.debug("LDAP says no such user '%s' (%s)", uid, username) + log.debug("LDAP says no such user '%s'", username) raise LdapUsernameError() except ldap.SERVER_DOWN: - raise LdapConnectionError("LDAP can't access authentication server") - - return dn, attrs + # [0] might be {'info': "TLS error -8179:Peer's Certificate issuer is not recognized.", 'desc': "Can't contact LDAP server"} + raise LdapConnectionError("LDAP can't connect to authentication server") class KallitheaAuthPlugin(auth_modules.KallitheaExternalAuthPlugin): @@ -192,11 +189,11 @@ }, { "name": "port", - "validator": self.validators.Number(strip=True, not_empty=True), + "validator": self.validators.Number(strip=True), "type": "string", - "description": "Port that the LDAP server is listening on", - "default": 389, - "formname": "Port" + "description": "Port that the LDAP server is listening on. Defaults to 389 for PLAIN/START_TLS and 636 for LDAPS.", + "default": "", + "formname": "Custom LDAP Port" }, { "name": "dn_user", @@ -230,6 +227,13 @@ "formname": "Certificate Checks" }, { + "name": "cacertdir", + "validator": self.validators.UnicodeString(strip=True), + "type": "string", + "description": "Optional: Custom CA certificate directory for validating LDAPS", + "formname": "Custom CA Certificates" + }, + { "name": "base_dn", "validator": self.validators.UnicodeString(strip=True), "type": "string", @@ -313,6 +317,7 @@ 'bind_pass': settings.get('dn_pass'), 'tls_kind': settings.get('tls_kind'), 'tls_reqcert': settings.get('tls_reqcert'), + 'cacertdir': settings.get('cacertdir'), 'ldap_filter': settings.get('filter'), 'search_scope': settings.get('search_scope'), 'attr_login': settings.get('attr_login'), @@ -353,12 +358,13 @@ log.info('user %s authenticated correctly', user_data['username']) return user_data - except (LdapUsernameError, LdapPasswordError, LdapImportError): - log.error(traceback.format_exc()) - return None - except (Exception,): - log.error(traceback.format_exc()) - return None + except LdapUsernameError: + log.info('Error authenticating %s with LDAP: User not found', username) + except LdapPasswordError: + log.info('Error authenticating %s with LDAP: Password error', username) + except LdapImportError: + log.error('Error authenticating %s with LDAP: LDAP not available', username) + return None def get_managed_fields(self): return ['username', 'firstname', 'lastname', 'email', 'password'] diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/base.py --- a/kallithea/lib/base.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/base.py Sat Dec 24 00:34:38 2016 +0100 @@ -38,15 +38,15 @@ import paste.auth.basic import paste.httpheaders -from pylons import config, tmpl_context as c, request, session, url +from pylons import config, tmpl_context as c, request, session from pylons.controllers import WSGIController -from pylons.controllers.util import redirect from pylons.templating import render_mako as render # don't remove this import from pylons.i18n.translation import _ from kallithea import __version__, BACKENDS -from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict,\ +from kallithea.config.routing import url +from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict, \ safe_str, safe_int from kallithea.lib import auth_modules from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware @@ -55,10 +55,9 @@ from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError from kallithea.model import meta -from kallithea.model.db import Repository, Ui, User, Setting +from kallithea.model.db import PullRequest, Repository, Ui, User, Setting from kallithea.model.notification import NotificationModel from kallithea.model.scm import ScmModel -from kallithea.model.pull_request import PullRequestModel log = logging.getLogger(__name__) @@ -117,7 +116,9 @@ auth_user = AuthUser(dbuser=user, is_external_auth=is_external_auth) - auth_user.set_authenticated() + # It should not be possible to explicitly log in as the default user. + assert not auth_user.is_default_user + auth_user.is_authenticated = True # Start new session to prevent session fixation attacks. session.invalidate() @@ -248,20 +249,6 @@ def _get_ip_addr(self, environ): return _get_ip_addr(environ) - def _check_ssl(self, environ): - """ - Checks the SSL check flag and returns False if SSL is not present - and required True otherwise - """ - #check if we have SSL required ! if not it's a bad request ! - if str2bool(Ui.get_by_key('push_ssl').ui_value): - org_proto = environ.get('wsgi._org_proto', environ['wsgi.url_scheme']) - if org_proto != 'https': - 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 @@ -332,7 +319,7 @@ c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon')) c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon')) c.visual.stylify_metatags = str2bool(rc_config.get('stylify_metatags')) - c.visual.dashboard_items = safe_int(rc_config.get('dashboard_items', 100)) + c.visual.page_size = safe_int(rc_config.get('dashboard_items', 100)) c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100)) c.visual.repository_fields = str2bool(rc_config.get('repository_fields')) c.visual.show_version = str2bool(rc_config.get('show_version')) @@ -366,12 +353,12 @@ c.repo_name = get_repo_slug(request) # can be empty c.backends = BACKENDS.keys() - c.unread_notifications = NotificationModel()\ + c.unread_notifications = NotificationModel() \ .get_unread_cnt_for_user(c.authuser.user_id) self.cut_off_limit = safe_int(config.get('cut_off_limit')) - c.my_pr_count = PullRequestModel().get_pullrequest_cnt_for_user(c.authuser.user_id) + c.my_pr_count = PullRequest.query(reviewer_id=c.authuser.user_id, include_closed=False).count() self.sa = meta.Session self.scm_model = ScmModel(self.sa) @@ -392,7 +379,9 @@ # Authenticate by session cookie # In ancient login sessions, 'authuser' may not be a dict. # In that case, the user will have to log in again. - if isinstance(session_authuser, dict): + # v0.3 and earlier included an 'is_authenticated' key; if present, + # this must be True. + if isinstance(session_authuser, dict) and session_authuser.get('is_authenticated', True): try: return AuthUser.from_cookie(session_authuser) except UserCreationError as e: @@ -479,7 +468,7 @@ if route in ['repo_creating_home']: return check_url = url('repo_creating_home', repo_name=c.repo_name) - return redirect(check_url) + raise webob.exc.HTTPFound(location=check_url) dbr = c.db_repo = _dbr c.db_repo_scm_instance = c.db_repo.scm_instance @@ -513,7 +502,8 @@ category='error') raise webob.exc.HTTPNotFound() except ChangesetDoesNotExistError as e: - h.flash(h.literal(_('Changeset not found')), + h.flash(h.literal(_('Changeset for %s %s not found in %s') % + (ref_type, ref_name, repo.repo_name)), category='error') raise webob.exc.HTTPNotFound() except RepositoryError as e: diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/caching_query.py --- a/kallithea/lib/caching_query.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/caching_query.py Sat Dec 24 00:34:38 2016 +0100 @@ -9,8 +9,6 @@ retrieves results in/from Beaker. * FromCache - a query option that establishes caching parameters on a Query - * RelationshipCache - a variant of FromCache which is specific - to a query invoked during a lazy load. * _params_from_query - extracts value parameters from a Query. @@ -54,7 +52,7 @@ by this query, which are usually literals defined in the WHERE clause. - The FromCache and RelationshipCache mapper options below represent + The FromCache mapper option below represent the "public" method of configuring this state upon the CachingQuery. """ @@ -212,65 +210,6 @@ self.cache_key) -class RelationshipCache(MapperOption): - """Specifies that a Query as called within a "lazy load" - should load results from a cache.""" - - propagate_to_loaders = True - - def __init__(self, region, namespace, attribute): - """Construct a new RelationshipCache. - - :param region: the cache region. Should be a - region configured in the Beaker CacheManager. - - :param namespace: the cache namespace. Should - be a name uniquely describing the target Query's - lexical structure. - - :param attribute: A Class.attribute which - indicates a particular class relationship() whose - lazy loader should be pulled from the cache. - - """ - self.region = region - self.namespace = namespace - self._relationship_options = { - (attribute.property.parent.class_, attribute.property.key): self - } - - def process_query_conditionally(self, query): - """Process a Query that is used within a lazy loader. - - (the process_query_conditionally() method is a SQLAlchemy - hook invoked only within lazyload.) - - """ - if query._current_path: - mapper, key = query._current_path[-2:] - - for cls in mapper.class_.__mro__: - if (cls, key) in self._relationship_options: - relationship_option = \ - self._relationship_options[(cls, key)] - _set_cache_parameters( - query, - relationship_option.region, - relationship_option.namespace, - None) - - def and_(self, option): - """Chain another RelationshipCache option to this one. - - While many RelationshipCache objects can be specified on a single - Query separately, chaining them together allows for a more efficient - lookup during load. - - """ - self._relationship_options.update(option._relationship_options) - return self - - def _params_from_query(query): """Pull the bind parameter values from a query. diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/celerylib/__init__.py --- a/kallithea/lib/celerylib/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/celerylib/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -26,20 +26,18 @@ """ -import socket -import traceback +import os import logging -from os.path import join as jn + from pylons import config from hashlib import md5 from decorator import decorator -from kallithea.lib.vcs.utils.lazy import LazyProperty from kallithea import CELERY_ON, CELERY_EAGER -from kallithea.lib.utils2 import str2bool, safe_str +from kallithea.lib.utils2 import safe_str from kallithea.lib.pidlock import DaemonLock, LockHeld -from kallithea.model import init_model +from kallithea.model.base import init_model from kallithea.model import meta from sqlalchemy import engine_from_config @@ -48,36 +46,49 @@ log = logging.getLogger(__name__) -class ResultWrapper(object): - def __init__(self, task): - self.task = task +class FakeTask(object): + """Fake a sync result to make it look like a finished task""" + + def __init__(self, result): + self.result = result - @LazyProperty - def result(self): - return self.task + def failed(self): + return False + + traceback = None # if failed + + task_id = None -def run_task(task, *args, **kwargs): - global CELERY_ON - if CELERY_ON: - try: - t = task.apply_async(args=args, kwargs=kwargs) - log.info('running task %s:%s', t.task_id, task) - return t +def task(f_org): + """Wrapper of celery.task.task, running async if CELERY_ON + """ - except socket.error as e: - if isinstance(e, IOError) and e.errno == 111: - log.debug('Unable to connect to celeryd. Sync execution') - CELERY_ON = False - else: - log.error(traceback.format_exc()) - except KeyError as e: - log.debug('Unable to connect to celeryd. Sync execution') - except Exception as e: - log.error(traceback.format_exc()) + if CELERY_ON: + def f_async(*args, **kwargs): + log.info('executing %s task', f_org.__name__) + try: + f_org(*args, **kwargs) + finally: + log.info('executed %s task', f_org.__name__) + f_async.__name__ = f_org.__name__ + from kallithea.lib import celerypylons + runner = celerypylons.task(ignore_result=True)(f_async) + def f_wrapped(*args, **kwargs): + t = runner.apply_async(args=args, kwargs=kwargs) + log.info('executing task %s in async mode - id %s', f_org, t.task_id) + return t + else: + def f_wrapped(*args, **kwargs): + log.info('executing task %s in sync', f_org.__name__) + try: + result = f_org(*args, **kwargs) + except Exception as e: + log.error('exception executing sync task %s in sync', f_org.__name__, e) + raise # TODO: return this in FakeTask as with async tasks? + return FakeTask(result) - log.debug('executing task %s in sync mode', task) - return ResultWrapper(task(*args, **kwargs)) + return f_wrapped def __get_lockkey(func, *fargs, **fkwargs): @@ -98,7 +109,7 @@ log.info('running task with lockkey %s', lockkey) try: - l = DaemonLock(file_=jn(lockkey_path, lockkey)) + l = DaemonLock(file_=os.path.join(lockkey_path, lockkey)) ret = func(*fargs, **fkwargs) l.release() return ret @@ -111,7 +122,7 @@ def get_session(): if CELERY_ON: - engine = engine_from_config(config, 'sqlalchemy.db1.') + engine = engine_from_config(config, 'sqlalchemy.') init_model(engine) sa = meta.Session() return sa diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/celerylib/tasks.py --- a/kallithea/lib/celerylib/tasks.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/celerylib/tasks.py Sat Dec 24 00:34:38 2016 +0100 @@ -26,13 +26,10 @@ :license: GPLv3, see LICENSE.md for more details. """ -from celery.decorators import task - import os import traceback import logging import rfc822 -from os.path import join as jn from time import mktime from operator import itemgetter @@ -41,11 +38,11 @@ from pylons import config from kallithea import CELERY_ON -from kallithea.lib.celerylib import run_task, locked_task, dbsession, \ - str2bool, __get_lockkey, LockHeld, DaemonLock, get_session +from kallithea.lib import celerylib from kallithea.lib.helpers import person from kallithea.lib.rcmail.smtp_mailer import SmtpMailer -from kallithea.lib.utils import add_cache, action_logger +from kallithea.lib.utils import setup_cache_regions, action_logger +from kallithea.lib.utils2 import str2bool from kallithea.lib.vcs.utils import author_email from kallithea.lib.compat import json, OrderedDict from kallithea.lib.hooks import log_create_repository @@ -53,46 +50,39 @@ from kallithea.model.db import Statistics, Repository, User -add_cache(config) # pragma: no cover +setup_cache_regions(config) # pragma: no cover __all__ = ['whoosh_index', 'get_commits_stats', 'send_email'] -def get_logger(cls): - if CELERY_ON: - try: - return cls.get_logger() - except AttributeError: - pass - return logging.getLogger(__name__) +log = logging.getLogger(__name__) -@task(ignore_result=True) -@locked_task -@dbsession +@celerylib.task +@celerylib.locked_task +@celerylib.dbsession def whoosh_index(repo_location, full_index): from kallithea.lib.indexers.daemon import WhooshIndexingDaemon - DBS = get_session() + DBS = celerylib.get_session() index_location = config['index_dir'] WhooshIndexingDaemon(index_location=index_location, - repo_location=repo_location, sa=DBS)\ + repo_location=repo_location, sa=DBS) \ .run(full_index=full_index) -@task(ignore_result=True) -@dbsession +@celerylib.task +@celerylib.dbsession def get_commits_stats(repo_name, ts_min_y, ts_max_y, recurse_limit=100): - log = get_logger(get_commits_stats) - DBS = get_session() - lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y, + DBS = celerylib.get_session() + lockkey = celerylib.__get_lockkey('get_commits_stats', repo_name, ts_min_y, ts_max_y) lockkey_path = config['app_conf']['cache_dir'] log.info('running task with lockkey %s', lockkey) try: - lock = l = DaemonLock(file_=jn(lockkey_path, lockkey)) + lock = l = celerylib.DaemonLock(file_=os.path.join(lockkey_path, lockkey)) # for js data compatibility cleans the key for person from ' akc = lambda k: person(k).replace('"', "") @@ -116,9 +106,9 @@ last_cs = None timegetter = itemgetter('time') - dbrepo = DBS.query(Repository)\ + dbrepo = DBS.query(Repository) \ .filter(Repository.repo_name == repo_name).scalar() - cur_stats = DBS.query(Statistics)\ + cur_stats = DBS.query(Statistics) \ .filter(Statistics.repository == dbrepo).scalar() if cur_stats is not None: @@ -176,7 +166,7 @@ "changed": len(cs.changed), "removed": len(cs.removed), } - co_day_auth_aggr[akc(cs.author)]['data']\ + co_day_auth_aggr[akc(cs.author)]['data'] \ .append(datadict) else: @@ -236,19 +226,18 @@ # execute another task if celery is enabled if len(repo.revisions) > 1 and CELERY_ON and recurse_limit > 0: - recurse_limit -= 1 - run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y, - recurse_limit) - if recurse_limit <= 0: - log.debug('Breaking recursive mode due to reach of recurse limit') - return True - except LockHeld: - log.info('LockHeld') + get_commits_stats(repo_name, ts_min_y, ts_max_y, recurse_limit - 1) + elif recurse_limit <= 0: + log.debug('Not recursing - limit has been reached') + else: + log.debug('Not recursing') + except celerylib.LockHeld: + log.info('Task with key %s already running', lockkey) return 'Task with key %s already running' % lockkey -@task(ignore_result=True) -@dbsession +@celerylib.task +@celerylib.dbsession def send_email(recipients, subject, body='', html_body='', headers=None, author=None): """ Sends an email with defined parameters from the .ini files. @@ -261,7 +250,6 @@ :param headers: dictionary of prepopulated e-mail headers :param author: User object of the author of this mail, if known and relevant """ - log = get_logger(send_email) assert isinstance(recipients, list), recipients if headers is None: headers = {} @@ -335,15 +323,14 @@ return False return True -@task(ignore_result=False) -@dbsession +@celerylib.task +@celerylib.dbsession def create_repo(form_data, cur_user): from kallithea.model.repo import RepoModel from kallithea.model.user import UserModel from kallithea.model.db import Setting - log = get_logger(create_repo) - DBS = get_session() + DBS = celerylib.get_session() cur_user = UserModel(DBS)._get_user(cur_user) @@ -417,15 +404,11 @@ RepoModel(DBS)._delete_filesystem_repo(repo) raise - # it's an odd fix to make celery fail task when exception occurs - def on_failure(self, *args, **kwargs): - pass - return True -@task(ignore_result=False) -@dbsession +@celerylib.task +@celerylib.dbsession def create_repo_fork(form_data, cur_user): """ Creates a fork of repository using interval VCS methods @@ -436,8 +419,7 @@ from kallithea.model.repo import RepoModel from kallithea.model.user import UserModel - log = get_logger(create_repo_fork) - DBS = get_session() + DBS = celerylib.get_session() base_path = Repository.base_path() cur_user = UserModel(DBS)._get_user(cur_user) @@ -502,10 +484,6 @@ RepoModel(DBS)._delete_filesystem_repo(repo) raise - # it's an odd fix to make celery fail task when exception occurs - def on_failure(self, *args, **kwargs): - pass - return True diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/celerypylons/__init__.py --- a/kallithea/lib/celerypylons/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/celerypylons/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -1,19 +1,36 @@ # -*- coding: utf-8 -*- """ -Automatically sets the environment variable `CELERY_LOADER` to -`celerypylons.loader:PylonsLoader`. This ensures the loader is -specified when accessing the rest of this package, and allows celery -to be installed in a webapp just by importing celerypylons:: +Kallithea wrapper of Celery + +The Celery configuration is in the normal Pylons ini file. We thus have to set +the `CELERY_LOADER` environment variable to point at a custom "loader" that can +read it. That environment variable must be set *before* importing celery. To +ensure that, we wrap celery in this module. - import celerypylons +Also, the loader depends on Pylons being configured to it can read the Celery +configuration out of it. To make sure that really is the case and give an early +warning, we check one of the mandatory settings. +This module must thus not be imported in global scope but must be imported on +demand in function scope. """ import os import warnings +# Verify Pylons configuration has been loaded +from pylons import config +assert config['celery.imports'] == 'kallithea.lib.celerylib.tasks', 'Kallithea Celery configuration has not been loaded' + +# Prepare environment to point at Kallithea Pylons loader CELERYPYLONS_LOADER = 'kallithea.lib.celerypylons.loader.PylonsLoader' if os.environ.get('CELERY_LOADER', CELERYPYLONS_LOADER) != CELERYPYLONS_LOADER: warnings.warn("'CELERY_LOADER' environment variable will be overridden by celery-pylons.") os.environ['CELERY_LOADER'] = CELERYPYLONS_LOADER + +# Import (and expose) celery, thus immediately triggering use of the custom Pylons loader +import celery.app as app +import celery.result as result +from celery.task import task +from celery.bin import worker diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/celerypylons/commands.py --- a/kallithea/lib/celerypylons/commands.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- - -import kallithea -from kallithea.lib.utils import BasePasterCommand, Command, load_rcextensions -from celery.app import app_or_default -from celery.bin import camqadm, celerybeat, celeryd, celeryev - -from kallithea.lib.utils2 import str2bool - -__all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand', - 'CAMQPAdminCommand', 'CeleryEventCommand'] - - -class CeleryCommand(BasePasterCommand): - """Abstract class implements run methods needed for celery - - Starts the celery worker that uses a paste.deploy configuration - file. - """ - - def update_parser(self): - """ - Abstract method. Allows for the class's parser to be updated - before the superclass's `run` method is called. Necessary to - allow options/arguments to be passed through to the underlying - celery command. - """ - - cmd = self.celery_command(app_or_default()) - for x in cmd.get_options(): - self.parser.add_option(x) - - def command(self): - from pylons import config - try: - CELERY_ON = str2bool(config['app_conf'].get('use_celery')) - except KeyError: - CELERY_ON = False - - if not CELERY_ON: - raise Exception('Please set use_celery = true in .ini config ' - 'file before running celeryd') - kallithea.CELERY_ON = CELERY_ON - load_rcextensions(config['here']) - cmd = self.celery_command(app_or_default()) - return cmd.run(**vars(self.options)) - - -class CeleryDaemonCommand(CeleryCommand): - """Start the celery worker - - Starts the celery worker that uses a paste.deploy configuration - file. - """ - usage = 'CONFIG_FILE [celeryd options...]' - summary = __doc__.splitlines()[0] - description = "".join(__doc__.splitlines()[2:]) - - parser = Command.standard_parser(quiet=True) - celery_command = celeryd.WorkerCommand - - -class CeleryBeatCommand(CeleryCommand): - """Start the celery beat server - - Starts the celery beat server using a paste.deploy configuration - file. - """ - usage = 'CONFIG_FILE [celerybeat options...]' - summary = __doc__.splitlines()[0] - description = "".join(__doc__.splitlines()[2:]) - - parser = Command.standard_parser(quiet=True) - celery_command = celerybeat.BeatCommand - - -class CAMQPAdminCommand(CeleryCommand): - """CAMQP Admin - - CAMQP celery admin tool. - """ - usage = 'CONFIG_FILE [camqadm options...]' - summary = __doc__.splitlines()[0] - description = "".join(__doc__.splitlines()[2:]) - - parser = Command.standard_parser(quiet=True) - celery_command = camqadm.AMQPAdminCommand - - -class CeleryEventCommand(CeleryCommand): - """Celery event command. - - Capture celery events. - """ - usage = 'CONFIG_FILE [celeryev options...]' - summary = __doc__.splitlines()[0] - description = "".join(__doc__.splitlines()[2:]) - - parser = Command.standard_parser(quiet=True) - celery_command = celeryev.EvCommand diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/celerypylons/loader.py --- a/kallithea/lib/celerypylons/loader.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/celerypylons/loader.py Sat Dec 24 00:34:38 2016 +0100 @@ -3,38 +3,45 @@ from celery.loaders.base import BaseLoader from pylons import config +# TODO: drop this mangling and just use a separate celery config section to_pylons = lambda x: x.replace('_', '.').lower() to_celery = lambda x: x.replace('.', '_').upper() -LIST_PARAMS = """CELERY_IMPORTS ADMINS ROUTES""".split() +LIST_PARAMS = """CELERY_IMPORTS ADMINS ROUTES CELERY_ACCEPT_CONTENT""".split() class PylonsSettingsProxy(object): """Pylons Settings Proxy - Proxies settings from pylons.config + Make settings from pylons.config appear the way Celery expects them. + """ + + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) - """ - def __getattr__(self, key): + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + + def __getitem__(self, key): + pylons_key = to_pylons(key) + value = config[pylons_key] + if key in LIST_PARAMS: + return value.split() + return self.type_converter(value) + + def __contains__(self, key): pylons_key = to_pylons(key) try: - value = config[pylons_key] - if key in LIST_PARAMS:return value.split() - return self.type_converter(value) + config[pylons_key] except KeyError: - raise AttributeError(pylons_key) - - def get(self, key): - try: - return self.__getattr__(key) - except AttributeError: - return None - - def __getitem__(self, key): - try: - return self.__getattr__(key) - except AttributeError: - raise KeyError() + return False + return True def __setattr__(self, key, value): pylons_key = to_pylons(key) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/colored_formatter.py --- a/kallithea/lib/colored_formatter.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/colored_formatter.py Sat Dec 24 00:34:38 2016 +0100 @@ -42,19 +42,19 @@ def format_sql(sql): sql = sql.replace('\n', '') sql = one_space_trim(sql) - sql = sql\ - .replace(',', ',\n\t')\ - .replace('SELECT', '\n\tSELECT \n\t')\ - .replace('UPDATE', '\n\tUPDATE \n\t')\ - .replace('DELETE', '\n\tDELETE \n\t')\ - .replace('FROM', '\n\tFROM')\ - .replace('ORDER BY', '\n\tORDER BY')\ - .replace('LIMIT', '\n\tLIMIT')\ - .replace('WHERE', '\n\tWHERE')\ - .replace('AND', '\n\tAND')\ - .replace('LEFT', '\n\tLEFT')\ - .replace('INNER', '\n\tINNER')\ - .replace('INSERT', '\n\tINSERT')\ + sql = sql \ + .replace(',', ',\n\t') \ + .replace('SELECT', '\n\tSELECT \n\t') \ + .replace('UPDATE', '\n\tUPDATE \n\t') \ + .replace('DELETE', '\n\tDELETE \n\t') \ + .replace('FROM', '\n\tFROM') \ + .replace('ORDER BY', '\n\tORDER BY') \ + .replace('LIMIT', '\n\tLIMIT') \ + .replace('WHERE', '\n\tWHERE') \ + .replace('AND', '\n\tAND') \ + .replace('LEFT', '\n\tLEFT') \ + .replace('INNER', '\n\tINNER') \ + .replace('INSERT', '\n\tINSERT') \ .replace('DELETE', '\n\tDELETE') return sql diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/compat.py --- a/kallithea/lib/compat.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/compat.py Sat Dec 24 00:34:38 2016 +0100 @@ -27,10 +27,9 @@ """ +import sys import os import functools -import importlib -from kallithea import __py_version__, is_windows #============================================================================== # json @@ -40,37 +39,18 @@ # alias for formatted json formatted_json = functools.partial(json.dumps, indent=4, sort_keys=True) -if __py_version__ >= (2, 7): + +#============================================================================== +# unittest +#============================================================================== +if sys.version_info >= (2, 7): import unittest else: import unittest2 as unittest -#============================================================================== -# izip_longest -#============================================================================== -try: - from itertools import izip_longest -except ImportError: - import itertools - - def izip_longest(*args, **kwds): - fillvalue = kwds.get("fillvalue") - - def sentinel(counter=([fillvalue] * (len(args) - 1)).pop): - yield counter() # yields the fillvalue, or raises IndexError - - fillers = itertools.repeat(fillvalue) - iters = [itertools.chain(it, sentinel(), fillers) - for it in args] - try: - for tup in itertools.izip(*iters): - yield tup - except IndexError: - pass - #============================================================================== -# OrderedDict +# OrderedDict - Python 2.7 could perhaps use collections.OrderedDict #============================================================================== # Python Software Foundation License @@ -377,13 +357,13 @@ #============================================================================== # Hybrid property/method #============================================================================== -from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property +from sqlalchemy.ext.hybrid import hybrid_property #============================================================================== -# kill FUNCTIONS +# kill #============================================================================== -if is_windows: +if os.name == 'nt': # Windows import ctypes def kill(pid, sig): @@ -394,175 +374,3 @@ else: kill = os.kill - - -#============================================================================== -# itertools.product -#============================================================================== - -try: - from itertools import product -except ImportError: - def product(*args, **kwds): - # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy - # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 - pools = map(tuple, args) * kwds.get('repeat', 1) - result = [[]] - for pool in pools: - 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 - - -#============================================================================== -# bytes -#============================================================================== -if __py_version__ >= (2, 6): - _bytes = bytes -else: - # in py2.6 bytes is a synonym for str - _bytes = str - -if __py_version__ >= (2, 6): - _bytearray = bytearray -else: - import array - # no idea if this is correct but all integration tests are passing - # i think we never use bytearray anyway - _bytearray = array - - -#============================================================================== -# 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 or -1 - 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 diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/db_manage.py --- a/kallithea/lib/db_manage.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/db_manage.py Sat Dec 24 00:34:38 2016 +0100 @@ -31,14 +31,16 @@ import time import uuid import logging -from os.path import dirname as dn, join as jn +from os.path import dirname -from kallithea import __dbversion__, __py_version__, EXTERN_TYPE_INTERNAL, DB_MIGRATIONS +import alembic.config +import alembic.command + +from kallithea.lib.paster_commands.common import ask_ok from kallithea.model.user import UserModel -from kallithea.lib.utils import ask_ok -from kallithea.model import init_model +from kallithea.model.base import init_model from kallithea.model.db import User, Permission, Ui, \ - Setting, UserToPerm, DbMigrateVersion, RepoGroup, \ + Setting, UserToPerm, RepoGroup, \ UserRepoGroupToPerm, CacheInvalidation, Repository from sqlalchemy.engine import create_engine @@ -61,20 +63,22 @@ class DbManage(object): - def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args={}): + def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args=None): self.dbname = dbconf.split('/')[-1] self.tests = tests self.root = root self.dburi = dbconf self.log_sql = log_sql self.db_exists = False - self.cli_args = cli_args + self.cli_args = cli_args or {} self.init_db(SESSION=SESSION) + def _ask_ok(self, msg): + """Invoke ask_ok unless the force_ask option provides the answer""" force_ask = self.cli_args.get('force_ask') if force_ask is not None: - global ask_ok - ask_ok = lambda *args, **kwargs: force_ask + return force_ask + return ask_ok(msg) def init_db(self, SESSION=None): if SESSION: @@ -94,7 +98,7 @@ if self.tests: destroy = True else: - destroy = ask_ok('Are you sure to destroy old database ? [y/n]') + destroy = self._ask_ok('Are you sure to destroy old database ? [y/n]') if not destroy: print 'Nothing done.' sys.exit(0) @@ -103,89 +107,27 @@ checkfirst = not override Base.metadata.create_all(checkfirst=checkfirst) - log.info('Created tables for %s', self.dbname) - - def set_db_version(self): - ver = DbMigrateVersion() - ver.version = __dbversion__ - ver.repository_id = DB_MIGRATIONS - ver.repository_path = 'versions' - self.sa.add(ver) - log.info('db version set to: %s', __dbversion__) - - def upgrade(self): - """ - Upgrades given database schema to given revision following - all needed steps, to perform the upgrade - - """ - - from kallithea.lib.dbmigrate.migrate.versioning import api - from kallithea.lib.dbmigrate.migrate.exceptions import \ - DatabaseNotControlledError - - if 'sqlite' in self.dburi: - print ( - '********************** WARNING **********************\n' - 'Make sure your version of sqlite is at least 3.7.X. \n' - 'Earlier versions are known to fail on some migrations\n' - '*****************************************************\n') - - upgrade = ask_ok('You are about to perform database upgrade, make ' - 'sure You backed up your database before. ' - 'Continue ? [y/n]') - if not upgrade: - print 'No upgrade performed' - sys.exit(0) - repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))), - 'kallithea/lib/dbmigrate') - db_uri = self.dburi - - try: - curr_version = api.db_version(db_uri, repository_path) - msg = ('Found current database under version ' - 'control with version %s' % curr_version) - - except (RuntimeError, DatabaseNotControlledError): - curr_version = 1 - msg = ('Current database is not under version control. Setting ' - 'as version %s' % curr_version) - api.version_control(db_uri, repository_path, curr_version) - - notify(msg) - if curr_version == __dbversion__: - print 'This database is already at the newest version' - sys.exit(0) + # Create an Alembic configuration and generate the version table, + # "stamping" it with the most recent Alembic migration revision, to + # tell Alembic that all the schema upgrades are already in effect. + alembic_cfg = alembic.config.Config() + alembic_cfg.set_main_option('script_location', 'kallithea:alembic') + alembic_cfg.set_main_option('sqlalchemy.url', self.dburi) + # This command will give an error in an Alembic multi-head scenario, + # but in practice, such a scenario should not come up during database + # creation, even during development. + alembic.command.stamp(alembic_cfg, 'head') - # clear cache keys - log.info("Clearing cache keys now...") - CacheInvalidation.clear_cache() - - upgrade_steps = range(curr_version + 1, __dbversion__ + 1) - notify('attempting to do database upgrade from ' - 'version %s to version %s' % (curr_version, __dbversion__)) - - # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE - _step = None - for step in upgrade_steps: - notify('performing upgrade step %s' % step) - time.sleep(0.5) - - api.upgrade(db_uri, repository_path, step) - notify('schema upgrade for step %s completed' % (step,)) - - _step = step - - notify('upgrade to version %s successful' % _step) + log.info('Created tables for %s', self.dbname) def fix_repo_paths(self): """ Fixes a old kallithea version path into new one without a '*' """ - paths = self.sa.query(Ui)\ - .filter(Ui.ui_key == '/')\ + paths = self.sa.query(Ui) \ + .filter(Ui.ui_key == '/') \ .scalar() paths.ui_value = paths.ui_value.replace('*', '') @@ -198,8 +140,8 @@ Fixes a old default user with some 'nicer' default values, used mostly for anonymous access """ - def_user = self.sa.query(User)\ - .filter(User.username == User.DEFAULT_USER)\ + def_user = self.sa.query(User) \ + .filter(User.username == User.DEFAULT_USER) \ .one() def_user.name = 'Anonymous' @@ -223,11 +165,9 @@ if not self.tests: import getpass - # defaults - defaults = self.cli_args - username = defaults.get('username') - password = defaults.get('password') - email = defaults.get('email') + username = self.cli_args.get('username') + password = self.cli_args.get('password') + email = self.cli_args.get('email') def get_password(): password = getpass.getpass('Specify admin password ' @@ -256,7 +196,7 @@ self.create_user(username, password, email, True) else: log.info('creating admin and regular test users') - from kallithea.tests import TEST_USER_ADMIN_LOGIN, \ + from kallithea.tests.base 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, \ @@ -278,7 +218,7 @@ #HOOKS hooks1_key = Ui.HOOK_UPDATE - hooks1_ = self.sa.query(Ui)\ + hooks1_ = self.sa.query(Ui) \ .filter(Ui.ui_key == hooks1_key).scalar() hooks1 = Ui() if hooks1_ is None else hooks1_ @@ -289,7 +229,7 @@ self.sa.add(hooks1) hooks2_key = Ui.HOOK_REPO_SIZE - hooks2_ = self.sa.query(Ui)\ + hooks2_ = self.sa.query(Ui) \ .filter(Ui.ui_key == hooks2_key).scalar() hooks2 = Ui() if hooks2_ is None else hooks2_ hooks2.ui_section = 'hooks' @@ -390,9 +330,9 @@ g.group_name = g.get_new_name(g.name) self.sa.add(g) # get default perm - default = UserRepoGroupToPerm.query()\ - .filter(UserRepoGroupToPerm.group == g)\ - .filter(UserRepoGroupToPerm.user == def_usr)\ + default = UserRepoGroupToPerm.query() \ + .filter(UserRepoGroupToPerm.group == g) \ + .filter(UserRepoGroupToPerm.user == def_usr) \ .scalar() if default is None: @@ -411,7 +351,7 @@ if not default_user: return - u2p = UserToPerm.query()\ + u2p = UserToPerm.query() \ .filter(UserToPerm.user == default_user).all() fixed = False if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS): @@ -422,11 +362,11 @@ return fixed def update_repo_info(self): - RepoModel.update_repoinfo() + for repo in Repository.query(): + repo.update_changeset_cache() def config_prompt(self, test_repo_path='', retries=3): - defaults = self.cli_args - _path = defaults.get('repos_location') + _path = self.cli_args.get('repos_location') if retries == 3: log.info('Setting up repositories config') @@ -458,7 +398,7 @@ # check write access, warn user about non writeable paths elif not os.access(path, os.W_OK) and path_ok: log.warning('No write permission to given path %s', path) - if not ask_ok('Given path %s is not writeable, do you want to ' + if not self._ask_ok('Given path %s is not writeable, do you want to ' 'continue with read only mode ? [y/n]' % (path,)): log.error('Canceled by user') sys.exit(-1) @@ -466,8 +406,10 @@ if retries == 0: sys.exit('max retries reached') if not path_ok: + if _path is not None: + sys.exit('Invalid repo path: %s' % _path) retries -= 1 - return self.config_prompt(test_repo_path, retries) + return self.config_prompt(test_repo_path, retries) # recursing!!! real_path = os.path.normpath(os.path.realpath(path)) @@ -481,9 +423,7 @@ self.create_ui_settings(path) ui_config = [ - ('web', 'push_ssl', 'false'), ('web', 'allow_archive', 'gz zip bz2'), - ('web', 'allow_push', '*'), ('web', 'baseurl', '/'), ('paths', '/', path), #('phases', 'publish', 'false') @@ -502,7 +442,7 @@ ('show_public_icon', True, 'bool'), ('show_private_icon', True, 'bool'), ('stylify_metatags', False, 'bool'), - ('dashboard_items', 100, 'int'), + ('dashboard_items', 100, 'int'), # TODO: call it page_size ('admin_grid_items', 25, 'int'), ('show_version', True, 'bool'), ('use_gravatar', True, 'bool'), @@ -522,9 +462,9 @@ def create_user(self, username, password, email='', admin=False): log.info('creating user %s', username) UserModel().create_or_update(username, password, email, - firstname='Kallithea', lastname='Admin', + firstname=u'Kallithea', lastname=u'Admin', active=True, admin=admin, - extern_type=EXTERN_TYPE_INTERNAL) + extern_type=User.DEFAULT_AUTH_TYPE) def create_default_user(self): log.info('creating default user') @@ -532,14 +472,13 @@ user = UserModel().create_or_update(username=User.DEFAULT_USER, password=str(uuid.uuid1())[:20], email='anonymous@kallithea-scm.org', - firstname='Anonymous', - lastname='User') + firstname=u'Anonymous', + lastname=u'User') # based on configuration options activate/deactivate this user which # controls anonymous access if self.cli_args.get('public_access') is False: log.info('Public access disabled') user.active = False - Session().add(user) Session().commit() def create_permissions(self): @@ -558,13 +497,3 @@ """ log.info('creating default user permissions') PermissionModel(self.sa).create_default_permissions(user=User.DEFAULT_USER) - - @staticmethod - def check_waitress(): - """ - Function executed at the end of setup - """ - if not __py_version__ >= (2, 6): - notify('Python2.5 detected, please switch ' - 'egg:waitress#main -> egg:Paste#http ' - 'in your .ini file') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/__init__.py --- a/kallithea/lib/dbmigrate/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/dbmigrate/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -1,75 +1,11 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate -~~~~~~~~~~~~~~~~~~~~~~~ +from paste.script.command import Command -Database migration modules - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Dec 11, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import logging - -from kallithea.lib.utils import BasePasterCommand, Command, add_cache -from kallithea.lib.db_manage import DbManage - -log = logging.getLogger(__name__) - +class UpgradeDb(Command): + hidden = True + summary = '(removed)' -class UpgradeDb(BasePasterCommand): - """Command used for paster to upgrade our database to newer version - """ - - max_args = 1 - min_args = 1 - - usage = "CONFIG_FILE" - summary = "Upgrades current db to newer version" - group_name = "Kallithea" - - parser = Command.standard_parser(verbose=True) - - def command(self): - from pylons import config - add_cache(config) - - db_uri = config['sqlalchemy.db1.url'] - dbmanage = DbManage(log_sql=True, dbconf=db_uri, - root=config['here'], tests=False, - cli_args=self.options.__dict__) - dbmanage.upgrade() - - def update_parser(self): - self.parser.add_option('--sql', - action='store_true', - dest='just_sql', - help="Prints upgrade sql for further investigation", - default=False) - - self.parser.add_option('--force-yes', - action='store_true', - dest='force_ask', - default=None, - help='Force yes to every question') - self.parser.add_option('--force-no', - action='store_false', - dest='force_ask', - default=None, - help='Force no to every question') + def run(self, args): + raise SystemExit( + 'The "paster upgrade-db" command has been removed; please see the docs:\n' + ' https://kallithea.readthedocs.io/en/default/upgrade.html' + ) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate.cfg --- a/kallithea/lib/dbmigrate/migrate.cfg Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -[db_settings] -# Used to identify which repository this database is versioned under. -# You can use the name of your project. -repository_id=kallithea_db_migrations - -# The name of the database table used to track the schema version. -# This name shouldn't already be used by your project. -# If this is changed once a database is under version control, you'll need to -# change the table name in each database too. -version_table=db_migrate_version - -# When committing a change script, Migrate will attempt to generate the -# sql for all supported databases; normally, if one of them fails - probably -# because you don't have that database installed - it is ignored and the -# commit continues, perhaps ending successfully. -# Databases in this list MUST compile successfully during a commit, or the -# entire commit will fail. List the databases your application will actually -# be using to ensure your updates to that database work properly. -# This must be a list; example: ['postgres','sqlite'] -required_dbs=['sqlite'] diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/__init__.py --- a/kallithea/lib/dbmigrate/migrate/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -""" - SQLAlchemy migrate provides two APIs :mod:`migrate.versioning` for - database schema version and repository management and - :mod:`migrate.changeset` that allows to define database schema changes - using Python. -""" - -from kallithea.lib.dbmigrate.migrate.versioning import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -__version__ = '0.7.3.dev' diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/__init__.py --- a/kallithea/lib/dbmigrate/migrate/changeset/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -""" - This module extends SQLAlchemy and provides additional DDL [#]_ - support. - - .. [#] SQL Data Definition Language -""" -import re -import warnings - -import sqlalchemy -from sqlalchemy import __version__ as _sa_version - -warnings.simplefilter('always', DeprecationWarning) - -_sa_version = tuple(int(re.match("\d+", x).group(0)) - for x in _sa_version.split(".")) -SQLA_06 = _sa_version >= (0, 6) -SQLA_07 = _sa_version >= (0, 7) - -del re -del _sa_version - -from kallithea.lib.dbmigrate.migrate.changeset.schema import * -from kallithea.lib.dbmigrate.migrate.changeset.constraint import * - -sqlalchemy.schema.Table.__bases__ += (ChangesetTable,) -sqlalchemy.schema.Column.__bases__ += (ChangesetColumn,) -sqlalchemy.schema.Index.__bases__ += (ChangesetIndex,) - -sqlalchemy.schema.DefaultClause.__bases__ += (ChangesetDefaultClause,) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/ansisql.py --- a/kallithea/lib/dbmigrate/migrate/changeset/ansisql.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,293 +0,0 @@ -""" - Extensions to SQLAlchemy for altering existing tables. - - At the moment, this isn't so much based off of ANSI as much as - things that just happen to work with multiple databases. -""" -import StringIO - -import sqlalchemy as sa -from sqlalchemy.schema import SchemaVisitor -from sqlalchemy.engine.default import DefaultDialect -from sqlalchemy.sql import ClauseElement -from sqlalchemy.schema import (ForeignKeyConstraint, - PrimaryKeyConstraint, - CheckConstraint, - UniqueConstraint, - Index) - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.changeset import constraint - -from sqlalchemy.schema import AddConstraint, DropConstraint -from sqlalchemy.sql.compiler import DDLCompiler -SchemaGenerator = SchemaDropper = DDLCompiler - - -class AlterTableVisitor(SchemaVisitor): - """Common operations for ``ALTER TABLE`` statements.""" - - # engine.Compiler looks for .statement - # when it spawns off a new compiler - statement = ClauseElement() - - def append(self, s): - """Append content to the SchemaIterator's query buffer.""" - - self.buffer.write(s) - - def execute(self): - """Execute the contents of the SchemaIterator's buffer.""" - try: - return self.connection.execute(self.buffer.getvalue()) - finally: - self.buffer.truncate(0) - - def __init__(self, dialect, connection, **kw): - self.connection = connection - self.buffer = StringIO.StringIO() - self.preparer = dialect.identifier_preparer - self.dialect = dialect - - def traverse_single(self, elem): - ret = super(AlterTableVisitor, self).traverse_single(elem) - if ret: - # adapt to 0.6 which uses a string-returning - # object - self.append(" %s" % ret) - - def _to_table(self, param): - """Returns the table object for the given param object.""" - if isinstance(param, (sa.Column, sa.Index, sa.schema.Constraint)): - ret = param.table - else: - ret = param - return ret - - def start_alter_table(self, param): - """Returns the start of an ``ALTER TABLE`` SQL-Statement. - - Use the param object to determine the table name and use it - for building the SQL statement. - - :param param: object to determine the table from - :type param: :class:`sqlalchemy.Column`, :class:`sqlalchemy.Index`, - :class:`sqlalchemy.schema.Constraint`, :class:`sqlalchemy.Table`, - or string (table name) - """ - table = self._to_table(param) - self.append('\nALTER TABLE %s ' % self.preparer.format_table(table)) - return table - - -class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator): - """Extends ansisql generator for column creation (alter table add col)""" - - def visit_column(self, column): - """Create a column (table already exists). - - :param column: column object - :type column: :class:`sqlalchemy.Column` instance - """ - if column.default is not None: - self.traverse_single(column.default) - - table = self.start_alter_table(column) - self.append("ADD ") - - self.append(self.get_column_specification(column)) - - for cons in column.constraints: - self.traverse_single(cons) - self.execute() - - # ALTER TABLE STATEMENTS - - # add indexes and unique constraints - if column.index_name: - Index(column.index_name,column).create() - elif column.unique_name: - constraint.UniqueConstraint(column, - name=column.unique_name).create() - - # SA bounds FK constraints to table, add manually - for fk in column.foreign_keys: - self.add_foreignkey(fk.constraint) - - # add primary key constraint if needed - if column.primary_key_name: - cons = constraint.PrimaryKeyConstraint(column, - name=column.primary_key_name) - cons.create() - - def add_foreignkey(self, fk): - self.connection.execute(AddConstraint(fk)) - -class ANSIColumnDropper(AlterTableVisitor, SchemaDropper): - """Extends ANSI SQL dropper for column dropping (``ALTER TABLE - DROP COLUMN``). - """ - - def visit_column(self, column): - """Drop a column from its table. - - :param column: the column object - :type column: :class:`sqlalchemy.Column` - """ - table = self.start_alter_table(column) - self.append('DROP COLUMN %s' % self.preparer.format_column(column)) - self.execute() - - -class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator): - """Manages changes to existing schema elements. - - Note that columns are schema elements; ``ALTER TABLE ADD COLUMN`` - is in SchemaGenerator. - - All items may be renamed. Columns can also have many of their properties - - type, for example - changed. - - Each function is passed a tuple, containing (object, name); where - object is a type of object you'd expect for that function - (ie. table for visit_table) and name is the object's new - name. NONE means the name is unchanged. - """ - - def visit_table(self, table): - """Rename a table. Other ops aren't supported.""" - self.start_alter_table(table) - self.append("RENAME TO %s" % self.preparer.quote(table.new_name, - table.quote)) - self.execute() - - def visit_index(self, index): - """Rename an index""" - if hasattr(self, '_validate_identifier'): - # SA <= 0.6.3 - self.append("ALTER INDEX %s RENAME TO %s" % ( - self.preparer.quote( - self._validate_identifier( - index.name, True), index.quote), - self.preparer.quote( - self._validate_identifier( - index.new_name, True), index.quote))) - else: - # SA >= 0.6.5 - self.append("ALTER INDEX %s RENAME TO %s" % ( - self.preparer.quote( - self._index_identifier( - index.name), index.quote), - self.preparer.quote( - self._index_identifier( - index.new_name), index.quote))) - self.execute() - - def visit_column(self, delta): - """Rename/change a column.""" - # ALTER COLUMN is implemented as several ALTER statements - keys = delta.keys() - if 'type' in keys: - self._run_subvisit(delta, self._visit_column_type) - if 'nullable' in keys: - self._run_subvisit(delta, self._visit_column_nullable) - if 'server_default' in keys: - # Skip 'default': only handle server-side defaults, others - # are managed by the app, not the db. - self._run_subvisit(delta, self._visit_column_default) - if 'name' in keys: - self._run_subvisit(delta, self._visit_column_name, start_alter=False) - - def _run_subvisit(self, delta, func, start_alter=True): - """Runs visit method based on what needs to be changed on column""" - table = self._to_table(delta.table) - col_name = delta.current_name - if start_alter: - self.start_alter_column(table, col_name) - ret = func(table, delta.result_column, delta) - self.execute() - - def start_alter_column(self, table, col_name): - """Starts ALTER COLUMN""" - self.start_alter_table(table) - self.append("ALTER COLUMN %s " % self.preparer.quote(col_name, table.quote)) - - def _visit_column_nullable(self, table, column, delta): - nullable = delta['nullable'] - if nullable: - self.append("DROP NOT NULL") - else: - self.append("SET NOT NULL") - - def _visit_column_default(self, table, column, delta): - default_text = self.get_column_default_string(column) - if default_text is not None: - self.append("SET DEFAULT %s" % default_text) - else: - self.append("DROP DEFAULT") - - def _visit_column_type(self, table, column, delta): - type_ = delta['type'] - type_text = str(type_.compile(dialect=self.dialect)) - self.append("TYPE %s" % type_text) - - def _visit_column_name(self, table, column, delta): - self.start_alter_table(table) - col_name = self.preparer.quote(delta.current_name, table.quote) - new_name = self.preparer.format_column(delta.result_column) - self.append('RENAME COLUMN %s TO %s' % (col_name, new_name)) - - -class ANSIConstraintCommon(AlterTableVisitor): - """ - Migrate's constraints require a separate creation function from - SA's: Migrate's constraints are created independently of a table; - SA's are created at the same time as the table. - """ - - def get_constraint_name(self, cons): - """Gets a name for the given constraint. - - If the name is already set it will be used otherwise the - constraint's :meth:`autoname ` - method is used. - - :param cons: constraint object - """ - if cons.name is not None: - ret = cons.name - else: - ret = cons.name = cons.autoname() - return self.preparer.quote(ret, cons.quote) - - def visit_migrate_primary_key_constraint(self, *p, **k): - self._visit_constraint(*p, **k) - - def visit_migrate_foreign_key_constraint(self, *p, **k): - self._visit_constraint(*p, **k) - - def visit_migrate_check_constraint(self, *p, **k): - self._visit_constraint(*p, **k) - - def visit_migrate_unique_constraint(self, *p, **k): - self._visit_constraint(*p, **k) - -class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator): - def _visit_constraint(self, constraint): - constraint.name = self.get_constraint_name(constraint) - self.append(self.process(AddConstraint(constraint))) - self.execute() - -class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper): - def _visit_constraint(self, constraint): - constraint.name = self.get_constraint_name(constraint) - self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade))) - self.execute() - - -class ANSIDialect(DefaultDialect): - columngenerator = ANSIColumnGenerator - columndropper = ANSIColumnDropper - schemachanger = ANSISchemaChanger - constraintgenerator = ANSIConstraintGenerator - constraintdropper = ANSIConstraintDropper diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/constraint.py --- a/kallithea/lib/dbmigrate/migrate/changeset/constraint.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,200 +0,0 @@ -""" - This module defines standalone schema constraint classes. -""" -from sqlalchemy import schema - -from kallithea.lib.dbmigrate.migrate.exceptions import * - - -class ConstraintChangeset(object): - """Base class for Constraint classes.""" - - def _normalize_columns(self, cols, table_name=False): - """Given: column objects or names; return col names and - (maybe) a table""" - colnames = [] - table = None - for col in cols: - if isinstance(col, schema.Column): - if col.table is not None and table is None: - table = col.table - if table_name: - col = '.'.join((col.table.name, col.name)) - else: - col = col.name - colnames.append(col) - return colnames, table - - def __do_imports(self, visitor_name, *a, **kw): - engine = kw.pop('engine', self.table.bind) - from kallithea.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor, - run_single_visitor) - visitorcallable = get_engine_visitor(engine, visitor_name) - run_single_visitor(engine, visitorcallable, self, *a, **kw) - - def create(self, *a, **kw): - """Create the constraint in the database. - - :param engine: the database engine to use. If this is \ - :keyword:`None` the instance's engine will be used - :type engine: :class:`sqlalchemy.engine.base.Engine` - :param connection: reuse connection instead of creating new one. - :type connection: :class:`sqlalchemy.engine.base.Connection` instance - """ - # TODO: set the parent here instead of in __init__ - self.__do_imports('constraintgenerator', *a, **kw) - - def drop(self, *a, **kw): - """Drop the constraint from the database. - - :param engine: the database engine to use. If this is - :keyword:`None` the instance's engine will be used - :param cascade: Issue CASCADE drop if database supports it - :type engine: :class:`sqlalchemy.engine.base.Engine` - :type cascade: bool - :param connection: reuse connection instead of creating new one. - :type connection: :class:`sqlalchemy.engine.base.Connection` instance - :returns: Instance with cleared columns - """ - self.cascade = kw.pop('cascade', False) - self.__do_imports('constraintdropper', *a, **kw) - # the spirit of Constraint objects is that they - # are immutable (just like in a DB. they're only ADDed - # or DROPped). - #self.columns.clear() - return self - - -class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint): - """Construct PrimaryKeyConstraint - - Migrate's additional parameters: - - :param cols: Columns in constraint. - :param table: If columns are passed as strings, this kw is required - :type table: Table instance - :type cols: strings or Column instances - """ - - __migrate_visit_name__ = 'migrate_primary_key_constraint' - - def __init__(self, *cols, **kwargs): - colnames, table = self._normalize_columns(cols) - table = kwargs.pop('table', table) - super(PrimaryKeyConstraint, self).__init__(*colnames, **kwargs) - if table is not None: - self._set_parent(table) - - def autoname(self): - """Mimic the database's automatic constraint names""" - return "%s_pkey" % self.table.name - - -class ForeignKeyConstraint(ConstraintChangeset, schema.ForeignKeyConstraint): - """Construct ForeignKeyConstraint - - Migrate's additional parameters: - - :param columns: Columns in constraint - :param refcolumns: Columns that this FK reffers to in another table. - :param table: If columns are passed as strings, this kw is required - :type table: Table instance - :type columns: list of strings or Column instances - :type refcolumns: list of strings or Column instances - """ - - __migrate_visit_name__ = 'migrate_foreign_key_constraint' - - def __init__(self, columns, refcolumns, *args, **kwargs): - colnames, table = self._normalize_columns(columns) - table = kwargs.pop('table', table) - refcolnames, reftable = self._normalize_columns(refcolumns, - table_name=True) - super(ForeignKeyConstraint, self).__init__( - colnames, refcolnames, *args,**kwargs - ) - if table is not None: - self._set_parent(table) - - @property - def referenced(self): - return [e.column for e in self.elements] - - @property - def reftable(self): - return self.referenced[0].table - - def autoname(self): - """Mimic the database's automatic constraint names""" - if hasattr(self.columns, 'keys'): - # SA <= 0.5 - firstcol = self.columns[self.columns.keys()[0]] - ret = "%(table)s_%(firstcolumn)s_fkey" % dict( - table=firstcol.table.name, - firstcolumn=firstcol.name,) - else: - # SA >= 0.6 - ret = "%(table)s_%(firstcolumn)s_fkey" % dict( - table=self.table.name, - firstcolumn=self.columns[0],) - return ret - - -class CheckConstraint(ConstraintChangeset, schema.CheckConstraint): - """Construct CheckConstraint - - Migrate's additional parameters: - - :param sqltext: Plain SQL text to check condition - :param columns: If not name is applied, you must supply this kw\ - to autoname constraint - :param table: If columns are passed as strings, this kw is required - :type table: Table instance - :type columns: list of Columns instances - :type sqltext: string - """ - - __migrate_visit_name__ = 'migrate_check_constraint' - - def __init__(self, sqltext, *args, **kwargs): - cols = kwargs.pop('columns', []) - if not cols and not kwargs.get('name', False): - raise InvalidConstraintError('You must either set "name"' - 'parameter or "columns" to autogenarate it.') - colnames, table = self._normalize_columns(cols) - table = kwargs.pop('table', table) - schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs) - if table is not None: - self._set_parent(table) - self.colnames = colnames - - def autoname(self): - return "%(table)s_%(cols)s_check" % \ - dict(table=self.table.name, cols="_".join(self.colnames)) - - -class UniqueConstraint(ConstraintChangeset, schema.UniqueConstraint): - """Construct UniqueConstraint - - Migrate's additional parameters: - - :param cols: Columns in constraint. - :param table: If columns are passed as strings, this kw is required - :type table: Table instance - :type cols: strings or Column instances - - .. versionadded:: 0.6.0 - """ - - __migrate_visit_name__ = 'migrate_unique_constraint' - - def __init__(self, *cols, **kwargs): - self.colnames, table = self._normalize_columns(cols) - table = kwargs.pop('table', table) - super(UniqueConstraint, self).__init__(*self.colnames, **kwargs) - if table is not None: - self._set_parent(table) - - def autoname(self): - """Mimic the database's automatic constraint names""" - return "%s_%s_key" % (self.table.name, '_'.join(self.colnames)) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/databases/__init__.py --- a/kallithea/lib/dbmigrate/migrate/changeset/databases/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -""" - This module contains database dialect specific changeset - implementations. -""" -__all__ = [ - 'postgres', - 'sqlite', - 'mysql', - 'oracle', -] diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/databases/firebird.py --- a/kallithea/lib/dbmigrate/migrate/changeset/databases/firebird.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -""" - Firebird database specific implementations of changeset classes. -""" -from sqlalchemy.databases import firebird as sa_base -from sqlalchemy.schema import PrimaryKeyConstraint -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.changeset import ansisql - - -FBSchemaGenerator = sa_base.FBDDLCompiler - -class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator): - """Firebird column generator implementation.""" - - -class FBColumnDropper(ansisql.ANSIColumnDropper): - """Firebird column dropper implementation.""" - - def visit_column(self, column): - """Firebird supports 'DROP col' instead of 'DROP COLUMN col' syntax - - Drop primary key and unique constraints if dropped column is referencing it.""" - if column.primary_key: - if column.table.primary_key.columns.contains_column(column): - column.table.primary_key.drop() - # TODO: recreate primary key if it references more than this column - - for index in column.table.indexes: - # "column in index.columns" causes problems as all - # column objects compare equal and return a SQL expression - if column.name in [col.name for col in index.columns]: - index.drop() - # TODO: recreate index if it references more than this column - - for cons in column.table.constraints: - if isinstance(cons,PrimaryKeyConstraint): - # will be deleted only when the column its on - # is deleted! - continue - - should_drop = column.name in cons.columns - if should_drop: - self.start_alter_table(column) - self.append("DROP CONSTRAINT ") - self.append(self.preparer.format_constraint(cons)) - self.execute() - # TODO: recreate unique constraint if it refenrences more than this column - - self.start_alter_table(column) - self.append('DROP %s' % self.preparer.format_column(column)) - self.execute() - - -class FBSchemaChanger(ansisql.ANSISchemaChanger): - """Firebird schema changer implementation.""" - - def visit_table(self, table): - """Rename table not supported""" - raise exceptions.NotSupportedError( - "Firebird does not support renaming tables.") - - def _visit_column_name(self, table, column, delta): - self.start_alter_table(table) - col_name = self.preparer.quote(delta.current_name, table.quote) - new_name = self.preparer.format_column(delta.result_column) - self.append('ALTER COLUMN %s TO %s' % (col_name, new_name)) - - def _visit_column_nullable(self, table, column, delta): - """Changing NULL is not supported""" - # TODO: http://www.firebirdfaq.org/faq103/ - raise exceptions.NotSupportedError( - "Firebird does not support altering NULL behavior.") - - -class FBConstraintGenerator(ansisql.ANSIConstraintGenerator): - """Firebird constraint generator implementation.""" - - -class FBConstraintDropper(ansisql.ANSIConstraintDropper): - """Firebird constraint dropper implementation.""" - - def cascade_constraint(self, constraint): - """Cascading constraints is not supported""" - raise exceptions.NotSupportedError( - "Firebird does not support cascading constraints") - - -class FBDialect(ansisql.ANSIDialect): - columngenerator = FBColumnGenerator - columndropper = FBColumnDropper - schemachanger = FBSchemaChanger - constraintgenerator = FBConstraintGenerator - constraintdropper = FBConstraintDropper diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/databases/mysql.py --- a/kallithea/lib/dbmigrate/migrate/changeset/databases/mysql.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -""" - MySQL database specific implementations of changeset classes. -""" - -from sqlalchemy.databases import mysql as sa_base -from sqlalchemy import types as sqltypes - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.changeset import ansisql - - -MySQLSchemaGenerator = sa_base.MySQLDDLCompiler - -class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator): - pass - - -class MySQLColumnDropper(ansisql.ANSIColumnDropper): - pass - - -class MySQLSchemaChanger(MySQLSchemaGenerator, ansisql.ANSISchemaChanger): - - def visit_column(self, delta): - table = delta.table - colspec = self.get_column_specification(delta.result_column) - if delta.result_column.autoincrement: - primary_keys = [c for c in table.primary_key.columns - if (c.autoincrement and - isinstance(c.type, sqltypes.Integer) and - not c.foreign_keys)] - - if primary_keys: - first = primary_keys.pop(0) - if first.name == delta.current_name: - colspec += " AUTO_INCREMENT" - old_col_name = self.preparer.quote(delta.current_name, table.quote) - - self.start_alter_table(table) - - self.append("CHANGE COLUMN %s " % old_col_name) - self.append(colspec) - self.execute() - - def visit_index(self, param): - # If MySQL can do this, I can't find how - raise exceptions.NotSupportedError("MySQL cannot rename indexes") - - -class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator): - pass - - -class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper): - def visit_migrate_check_constraint(self, *p, **k): - raise exceptions.NotSupportedError("MySQL does not support CHECK" - " constraints, use triggers instead.") - - -class MySQLDialect(ansisql.ANSIDialect): - columngenerator = MySQLColumnGenerator - columndropper = MySQLColumnDropper - schemachanger = MySQLSchemaChanger - constraintgenerator = MySQLConstraintGenerator - constraintdropper = MySQLConstraintDropper diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/databases/oracle.py --- a/kallithea/lib/dbmigrate/migrate/changeset/databases/oracle.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -""" - Oracle database specific implementations of changeset classes. -""" -import sqlalchemy as sa -from sqlalchemy.databases import oracle as sa_base - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 - - -if not SQLA_06: - OracleSchemaGenerator = sa_base.OracleSchemaGenerator -else: - OracleSchemaGenerator = sa_base.OracleDDLCompiler - - -class OracleColumnGenerator(OracleSchemaGenerator, ansisql.ANSIColumnGenerator): - pass - - -class OracleColumnDropper(ansisql.ANSIColumnDropper): - pass - - -class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger): - - def get_column_specification(self, column, **kwargs): - # Ignore the NOT NULL generated - override_nullable = kwargs.pop('override_nullable', None) - if override_nullable: - orig = column.nullable - column.nullable = True - ret = super(OracleSchemaChanger, self).get_column_specification( - column, **kwargs) - if override_nullable: - column.nullable = orig - return ret - - def visit_column(self, delta): - keys = delta.keys() - - if 'name' in keys: - self._run_subvisit(delta, - self._visit_column_name, - start_alter=False) - - if len(set(('type', 'nullable', 'server_default')).intersection(keys)): - self._run_subvisit(delta, - self._visit_column_change, - start_alter=False) - - def _visit_column_change(self, table, column, delta): - # Oracle cannot drop a default once created, but it can set it - # to null. We'll do that if default=None - # http://forums.oracle.com/forums/message.jspa?messageID=1273234#1273234 - dropdefault_hack = (column.server_default is None \ - and 'server_default' in delta.keys()) - # Oracle apparently doesn't like it when we say "not null" if - # the column's already not null. Fudge it, so we don't need a - # new function - notnull_hack = ((not column.nullable) \ - and ('nullable' not in delta.keys())) - # We need to specify NULL if we're removing a NOT NULL - # constraint - null_hack = (column.nullable and ('nullable' in delta.keys())) - - if dropdefault_hack: - column.server_default = sa.PassiveDefault(sa.sql.null()) - if notnull_hack: - column.nullable = True - colspec = self.get_column_specification(column, - override_nullable=null_hack) - if null_hack: - colspec += ' NULL' - if notnull_hack: - column.nullable = False - if dropdefault_hack: - column.server_default = None - - self.start_alter_table(table) - self.append("MODIFY (") - self.append(colspec) - self.append(")") - - -class OracleConstraintCommon(object): - - def get_constraint_name(self, cons): - # Oracle constraints can't guess their name like other DBs - if not cons.name: - raise exceptions.NotSupportedError( - "Oracle constraint names must be explicitly stated") - return cons.name - - -class OracleConstraintGenerator(OracleConstraintCommon, - ansisql.ANSIConstraintGenerator): - pass - - -class OracleConstraintDropper(OracleConstraintCommon, - ansisql.ANSIConstraintDropper): - pass - - -class OracleDialect(ansisql.ANSIDialect): - columngenerator = OracleColumnGenerator - columndropper = OracleColumnDropper - schemachanger = OracleSchemaChanger - constraintgenerator = OracleConstraintGenerator - constraintdropper = OracleConstraintDropper diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/databases/postgres.py --- a/kallithea/lib/dbmigrate/migrate/changeset/databases/postgres.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -""" - `PostgreSQL`_ database specific implementations of changeset classes. - - .. _`PostgreSQL`: http://www.postgresql.org/ -""" -from kallithea.lib.dbmigrate.migrate.changeset import ansisql - - -from sqlalchemy.databases import postgresql as sa_base -PGSchemaGenerator = sa_base.PGDDLCompiler - - -class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator): - """PostgreSQL column generator implementation.""" - pass - - -class PGColumnDropper(ansisql.ANSIColumnDropper): - """PostgreSQL column dropper implementation.""" - pass - - -class PGSchemaChanger(ansisql.ANSISchemaChanger): - """PostgreSQL schema changer implementation.""" - pass - - -class PGConstraintGenerator(ansisql.ANSIConstraintGenerator): - """PostgreSQL constraint generator implementation.""" - pass - - -class PGConstraintDropper(ansisql.ANSIConstraintDropper): - """PostgreSQL constraint dropper implementation.""" - pass - - -class PGDialect(ansisql.ANSIDialect): - columngenerator = PGColumnGenerator - columndropper = PGColumnDropper - schemachanger = PGSchemaChanger - constraintgenerator = PGConstraintGenerator - constraintdropper = PGConstraintDropper diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/databases/sqlite.py --- a/kallithea/lib/dbmigrate/migrate/changeset/databases/sqlite.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,153 +0,0 @@ -""" - `SQLite`_ database specific implementations of changeset classes. - - .. _`SQLite`: http://www.sqlite.org/ -""" -from UserDict import DictMixin -from copy import copy - -from sqlalchemy.databases import sqlite as sa_base - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 - -SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler - - -class SQLiteCommon(object): - - def _not_supported(self, op): - raise exceptions.NotSupportedError("SQLite does not support " - "%s; see http://www.sqlite.org/lang_altertable.html" % op) - - -class SQLiteHelper(SQLiteCommon): - - def recreate_table(self,table,column=None,delta=None): - table_name = self.preparer.format_table(table) - - # we remove all indexes so as not to have - # problems during copy and re-create - for index in table.indexes: - index.drop() - - self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name) - self.execute() - - insertion_string = self._modify_table(table, column, delta) - - table.create(bind=self.connection) - self.append(insertion_string % {'table_name': table_name}) - self.execute() - self.append('DROP TABLE migration_tmp') - self.execute() - - def visit_column(self, delta): - if isinstance(delta, DictMixin): - column = delta.result_column - table = self._to_table(delta.table) - else: - column = delta - table = self._to_table(column.table) - - self.recreate_table(table,column,delta) - -class SQLiteColumnGenerator(SQLiteSchemaGenerator, - ansisql.ANSIColumnGenerator, - # at the end so we get the normal - # visit_column by default - SQLiteHelper, - SQLiteCommon - ): - """SQLite ColumnGenerator""" - - def _modify_table(self, table, column, delta): - columns = ' ,'.join(map( - self.preparer.format_column, - [c for c in table.columns if c.name!=column.name])) - return ('INSERT INTO %%(table_name)s (%(cols)s) ' - 'SELECT %(cols)s from migration_tmp')%{'cols':columns} - - def visit_column(self,column): - if column.foreign_keys: - SQLiteHelper.visit_column(self,column) - else: - super(SQLiteColumnGenerator,self).visit_column(column) - -class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper): - """SQLite ColumnDropper""" - - def _modify_table(self, table, column, delta): - - columns = ' ,'.join(map(self.preparer.format_column, table.columns)) - return 'INSERT INTO %(table_name)s SELECT ' + columns + \ - ' from migration_tmp' - - def visit_column(self,column): - # For SQLite, we *have* to remove the column here so the table - # is re-created properly. - column.remove_from_table(column.table,unset_table=False) - super(SQLiteColumnDropper,self).visit_column(column) - - -class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger): - """SQLite SchemaChanger""" - - def _modify_table(self, table, column, delta): - return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' - - def visit_index(self, index): - """Does not support ALTER INDEX""" - self._not_supported('ALTER INDEX') - - -class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon): - - def visit_migrate_primary_key_constraint(self, constraint): - tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )" - cols = ', '.join(map(self.preparer.format_column, constraint.columns)) - tname = self.preparer.format_table(constraint.table) - name = self.get_constraint_name(constraint) - msg = tmpl % (name, tname, cols) - self.append(msg) - self.execute() - - def _modify_table(self, table, column, delta): - return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' - - def visit_migrate_foreign_key_constraint(self, *p, **k): - self.recreate_table(p[0].table) - - def visit_migrate_unique_constraint(self, *p, **k): - self.recreate_table(p[0].table) - - -class SQLiteConstraintDropper(ansisql.ANSIColumnDropper, - SQLiteCommon, - ansisql.ANSIConstraintCommon): - - def visit_migrate_primary_key_constraint(self, constraint): - tmpl = "DROP INDEX %s " - name = self.get_constraint_name(constraint) - msg = tmpl % (name) - self.append(msg) - self.execute() - - def visit_migrate_foreign_key_constraint(self, *p, **k): - self._not_supported('ALTER TABLE DROP CONSTRAINT') - - def visit_migrate_check_constraint(self, *p, **k): - self._not_supported('ALTER TABLE DROP CONSTRAINT') - - def visit_migrate_unique_constraint(self, *p, **k): - self._not_supported('ALTER TABLE DROP CONSTRAINT') - - -# TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index - -class SQLiteDialect(ansisql.ANSIDialect): - columngenerator = SQLiteColumnGenerator - columndropper = SQLiteColumnDropper - schemachanger = SQLiteSchemaChanger - constraintgenerator = SQLiteConstraintGenerator - constraintdropper = SQLiteConstraintDropper diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/databases/visitor.py --- a/kallithea/lib/dbmigrate/migrate/changeset/databases/visitor.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -""" - Module for visitor class mapping. -""" -import sqlalchemy as sa - -from kallithea.lib.dbmigrate.migrate.changeset import ansisql -from kallithea.lib.dbmigrate.migrate.changeset.databases import (sqlite, - postgres, - mysql, - oracle, - firebird) - - -# Map SA dialects to the corresponding Migrate extensions -DIALECTS = { - "default": ansisql.ANSIDialect, - "sqlite": sqlite.SQLiteDialect, - "postgres": postgres.PGDialect, - "postgresql": postgres.PGDialect, - "mysql": mysql.MySQLDialect, - "oracle": oracle.OracleDialect, - "firebird": firebird.FBDialect, -} - - -def get_engine_visitor(engine, name): - """ - Get the visitor implementation for the given database engine. - - :param engine: SQLAlchemy Engine - :param name: Name of the visitor - :type name: string - :type engine: Engine - :returns: visitor - """ - # TODO: link to supported visitors - return get_dialect_visitor(engine.dialect, name) - - -def get_dialect_visitor(sa_dialect, name): - """ - Get the visitor implementation for the given dialect. - - Finds the visitor implementation based on the dialect class and - returns and instance initialized with the given name. - - Binds dialect specific preparer to visitor. - """ - - # map sa dialect to migrate dialect and return visitor - sa_dialect_name = getattr(sa_dialect, 'name', 'default') - migrate_dialect_cls = DIALECTS[sa_dialect_name] - visitor = getattr(migrate_dialect_cls, name) - - # bind preparer - visitor.preparer = sa_dialect.preparer(sa_dialect) - - return visitor - -def run_single_visitor(engine, visitorcallable, element, - connection=None, **kwargs): - """Taken from :meth:`sqlalchemy.engine.base.Engine._run_single_visitor` - with support for migrate visitors. - """ - if connection is None: - conn = engine.contextual_connect(close_with_result=False) - else: - conn = connection - visitor = visitorcallable(engine.dialect, conn) - try: - if hasattr(element, '__migrate_visit_name__'): - fn = getattr(visitor, 'visit_' + element.__migrate_visit_name__) - else: - fn = getattr(visitor, 'visit_' + element.__visit_name__) - fn(element, **kwargs) - finally: - if connection is None: - conn.close() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/changeset/schema.py --- a/kallithea/lib/dbmigrate/migrate/changeset/schema.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,655 +0,0 @@ -""" - Schema module providing common schema operations. -""" -import warnings - -from UserDict import DictMixin - -import sqlalchemy - -from sqlalchemy.schema import ForeignKeyConstraint -from sqlalchemy.schema import UniqueConstraint - -from kallithea.lib.dbmigrate.migrate.exceptions import * -from kallithea.lib.dbmigrate.migrate.changeset import SQLA_06, SQLA_07 -from kallithea.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor, - run_single_visitor) - - -__all__ = [ - 'create_column', - 'drop_column', - 'alter_column', - 'rename_table', - 'rename_index', - 'ChangesetTable', - 'ChangesetColumn', - 'ChangesetIndex', - 'ChangesetDefaultClause', - 'ColumnDelta', -] - -def create_column(column, table=None, *p, **kw): - """Create a column, given the table. - - API to :meth:`ChangesetColumn.create`. - """ - if table is not None: - return table.create_column(column, *p, **kw) - return column.create(*p, **kw) - - -def drop_column(column, table=None, *p, **kw): - """Drop a column, given the table. - - API to :meth:`ChangesetColumn.drop`. - """ - if table is not None: - return table.drop_column(column, *p, **kw) - return column.drop(*p, **kw) - - -def rename_table(table, name, engine=None, **kw): - """Rename a table. - - If Table instance is given, engine is not used. - - API to :meth:`ChangesetTable.rename`. - - :param table: Table to be renamed. - :param name: New name for Table. - :param engine: Engine instance. - :type table: string or Table instance - :type name: string - :type engine: obj - """ - table = _to_table(table, engine) - table.rename(name, **kw) - - -def rename_index(index, name, table=None, engine=None, **kw): - """Rename an index. - - If Index instance is given, - table and engine are not used. - - API to :meth:`ChangesetIndex.rename`. - - :param index: Index to be renamed. - :param name: New name for index. - :param table: Table to which Index is reffered. - :param engine: Engine instance. - :type index: string or Index instance - :type name: string - :type table: string or Table instance - :type engine: obj - """ - index = _to_index(index, table, engine) - index.rename(name, **kw) - - -def alter_column(*p, **k): - """Alter a column. - - This is a helper function that creates a :class:`ColumnDelta` and - runs it. - - :argument column: - The name of the column to be altered or a - :class:`ChangesetColumn` column representing it. - - :param table: - A :class:`~sqlalchemy.schema.Table` or table name to - for the table where the column will be changed. - - :param engine: - The :class:`~sqlalchemy.engine.base.Engine` to use for table - reflection and schema alterations. - - :returns: A :class:`ColumnDelta` instance representing the change. - - - """ - - if 'table' not in k and isinstance(p[0], sqlalchemy.Column): - k['table'] = p[0].table - if 'engine' not in k: - k['engine'] = k['table'].bind - - # deprecation - if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column): - warnings.warn( - "Passing a Column object to alter_column is deprecated." - " Just pass in keyword parameters instead.", - MigrateDeprecationWarning - ) - engine = k['engine'] - - # enough tests seem to break when metadata is always altered - # that this crutch has to be left in until they can be sorted - # out - k['alter_metadata']=True - - delta = ColumnDelta(*p, **k) - - visitorcallable = get_engine_visitor(engine, 'schemachanger') - engine._run_visitor(visitorcallable, delta) - - return delta - - -def _to_table(table, engine=None): - """Return if instance of Table, else construct new with metadata""" - if isinstance(table, sqlalchemy.Table): - return table - - # Given: table name, maybe an engine - meta = sqlalchemy.MetaData() - if engine is not None: - meta.bind = engine - return sqlalchemy.Table(table, meta) - - -def _to_index(index, table=None, engine=None): - """Return if instance of Index, else construct new with metadata""" - if isinstance(index, sqlalchemy.Index): - return index - - # Given: index name; table name required - table = _to_table(table, engine) - ret = sqlalchemy.Index(index) - ret.table = table - return ret - - -class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem): - """Extracts the differences between two columns/column-parameters - - May receive parameters arranged in several different ways: - - * **current_column, new_column, \*p, \*\*kw** - Additional parameters can be specified to override column - differences. - - * **current_column, \*p, \*\*kw** - Additional parameters alter current_column. Table name is extracted - from current_column object. - Name is changed to current_column.name from current_name, - if current_name is specified. - - * **current_col_name, \*p, \*\*kw** - Table kw must specified. - - :param table: Table at which current Column should be bound to.\ - If table name is given, reflection will be used. - :type table: string or Table instance - - :param metadata: A :class:`MetaData` instance to store - reflected table names - - :param engine: When reflecting tables, either engine or metadata must \ - be specified to acquire engine object. - :type engine: :class:`Engine` instance - :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \ - `result_column` through :func:`dict` alike object. - - * :class:`ColumnDelta`.result_column is altered column with new attributes - - * :class:`ColumnDelta`.current_name is current name of column in db - - - """ - - # Column attributes that can be altered - diff_keys = ('name', 'type', 'primary_key', 'nullable', - 'server_onupdate', 'server_default', 'autoincrement') - diffs = dict() - __visit_name__ = 'column' - - def __init__(self, *p, **kw): - # 'alter_metadata' is not a public api. It exists purely - # as a crutch until the tests that fail when 'alter_metadata' - # behaviour always happens can be sorted out - self.alter_metadata = kw.pop("alter_metadata", False) - - self.meta = kw.pop("metadata", None) - self.engine = kw.pop("engine", None) - - # Things are initialized differently depending on how many column - # parameters are given. Figure out how many and call the appropriate - # method. - if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column): - # At least one column specified - if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column): - # Two columns specified - diffs = self.compare_2_columns(*p, **kw) - else: - # Exactly one column specified - diffs = self.compare_1_column(*p, **kw) - else: - # Zero columns specified - if not len(p) or not isinstance(p[0], basestring): - raise ValueError("First argument must be column name") - diffs = self.compare_parameters(*p, **kw) - - self.apply_diffs(diffs) - - def __repr__(self): - return '' % ( - self.alter_metadata, - super(ColumnDelta, self).__repr__() - ) - - def __getitem__(self, key): - if key not in self.keys(): - raise KeyError("No such diff key, available: %s" % self.diffs ) - return getattr(self.result_column, key) - - def __setitem__(self, key, value): - if key not in self.keys(): - raise KeyError("No such diff key, available: %s" % self.diffs ) - setattr(self.result_column, key, value) - - def __delitem__(self, key): - raise NotImplementedError - - def keys(self): - return self.diffs.keys() - - def compare_parameters(self, current_name, *p, **k): - """Compares Column objects with reflection""" - self.table = k.pop('table') - self.result_column = self._table.c.get(current_name) - if len(p): - k = self._extract_parameters(p, k, self.result_column) - return k - - def compare_1_column(self, col, *p, **k): - """Compares one Column object""" - self.table = k.pop('table', None) - if self.table is None: - self.table = col.table - self.result_column = col - if len(p): - k = self._extract_parameters(p, k, self.result_column) - return k - - def compare_2_columns(self, old_col, new_col, *p, **k): - """Compares two Column objects""" - self.process_column(new_col) - self.table = k.pop('table', None) - # we cannot use bool() on table in SA06 - if self.table is None: - self.table = old_col.table - if self.table is None: - new_col.table - self.result_column = old_col - - # set differences - # leave out some stuff for later comp - for key in (set(self.diff_keys) - set(('type',))): - val = getattr(new_col, key, None) - if getattr(self.result_column, key, None) != val: - k.setdefault(key, val) - - # inspect types - if not self.are_column_types_eq(self.result_column.type, new_col.type): - k.setdefault('type', new_col.type) - - if len(p): - k = self._extract_parameters(p, k, self.result_column) - return k - - def apply_diffs(self, diffs): - """Populate dict and column object with new values""" - self.diffs = diffs - for key in self.diff_keys: - if key in diffs: - setattr(self.result_column, key, diffs[key]) - - self.process_column(self.result_column) - - # create an instance of class type if not yet - if 'type' in diffs and callable(self.result_column.type): - self.result_column.type = self.result_column.type() - - # add column to the table - if self.table is not None and self.alter_metadata: - self.result_column.add_to_table(self.table) - - def are_column_types_eq(self, old_type, new_type): - """Compares two types to be equal""" - ret = old_type.__class__ == new_type.__class__ - - # String length is a special case - if ret and isinstance(new_type, sqlalchemy.types.String): - ret = (getattr(old_type, 'length', None) == \ - getattr(new_type, 'length', None)) - return ret - - def _extract_parameters(self, p, k, column): - """Extracts data from p and modifies diffs""" - p = list(p) - while len(p): - if isinstance(p[0], basestring): - k.setdefault('name', p.pop(0)) - elif isinstance(p[0], sqlalchemy.types.AbstractType): - k.setdefault('type', p.pop(0)) - elif callable(p[0]): - p[0] = p[0]() - else: - break - - if len(p): - new_col = column.copy_fixed() - new_col._init_items(*p) - k = self.compare_2_columns(column, new_col, **k) - return k - - def process_column(self, column): - """Processes default values for column""" - # XXX: this is a snippet from SA processing of positional parameters - toinit = list() - - if column.server_default is not None: - if isinstance(column.server_default, sqlalchemy.FetchedValue): - toinit.append(column.server_default) - else: - toinit.append(sqlalchemy.DefaultClause(column.server_default)) - if column.server_onupdate is not None: - if isinstance(column.server_onupdate, FetchedValue): - toinit.append(column.server_default) - else: - toinit.append(sqlalchemy.DefaultClause(column.server_onupdate, - for_update=True)) - if toinit: - column._init_items(*toinit) - - def _get_table(self): - return getattr(self, '_table', None) - - def _set_table(self, table): - if isinstance(table, basestring): - if self.alter_metadata: - if not self.meta: - raise ValueError("metadata must be specified for table" - " reflection when using alter_metadata") - meta = self.meta - if self.engine: - meta.bind = self.engine - else: - if not self.engine and not self.meta: - raise ValueError("engine or metadata must be specified" - " to reflect tables") - if not self.engine: - self.engine = self.meta.bind - meta = sqlalchemy.MetaData(bind=self.engine) - self._table = sqlalchemy.Table(table, meta, autoload=True) - elif isinstance(table, sqlalchemy.Table): - self._table = table - if not self.alter_metadata: - self._table.meta = sqlalchemy.MetaData(bind=self._table.bind) - def _get_result_column(self): - return getattr(self, '_result_column', None) - - def _set_result_column(self, column): - """Set Column to Table based on alter_metadata evaluation.""" - self.process_column(column) - if not hasattr(self, 'current_name'): - self.current_name = column.name - if self.alter_metadata: - self._result_column = column - else: - self._result_column = column.copy_fixed() - - table = property(_get_table, _set_table) - result_column = property(_get_result_column, _set_result_column) - - -class ChangesetTable(object): - """Changeset extensions to SQLAlchemy tables.""" - - def create_column(self, column, *p, **kw): - """Creates a column. - - The column parameter may be a column definition or the name of - a column in this table. - - API to :meth:`ChangesetColumn.create` - - :param column: Column to be created - :type column: Column instance or string - """ - if not isinstance(column, sqlalchemy.Column): - # It's a column name - column = getattr(self.c, str(column)) - column.create(table=self, *p, **kw) - - def drop_column(self, column, *p, **kw): - """Drop a column, given its name or definition. - - API to :meth:`ChangesetColumn.drop` - - :param column: Column to be dropped - :type column: Column instance or string - """ - if not isinstance(column, sqlalchemy.Column): - # It's a column name - try: - column = getattr(self.c, str(column)) - except AttributeError: - # That column isn't part of the table. We don't need - # its entire definition to drop the column, just its - # name, so create a dummy column with the same name. - column = sqlalchemy.Column(str(column), sqlalchemy.Integer()) - column.drop(table=self, *p, **kw) - - def rename(self, name, connection=None, **kwargs): - """Rename this table. - - :param name: New name of the table. - :type name: string - :param connection: reuse connection instead of creating new one. - :type connection: :class:`sqlalchemy.engine.base.Connection` instance - """ - engine = self.bind - self.new_name = name - visitorcallable = get_engine_visitor(engine, 'schemachanger') - run_single_visitor(engine, visitorcallable, self, connection, **kwargs) - - # Fix metadata registration - self.name = name - self.deregister() - self._set_parent(self.metadata) - - def _meta_key(self): - """Get the meta key for this table.""" - return sqlalchemy.schema._get_table_key(self.name, self.schema) - - def deregister(self): - """Remove this table from its metadata""" - if SQLA_07: - self.metadata._remove_table(self.name, self.schema) - else: - key = self._meta_key() - meta = self.metadata - if key in meta.tables: - del meta.tables[key] - - -class ChangesetColumn(object): - """Changeset extensions to SQLAlchemy columns.""" - - def alter(self, *p, **k): - """Makes a call to :func:`alter_column` for the column this - method is called on. - """ - if 'table' not in k: - k['table'] = self.table - if 'engine' not in k: - k['engine'] = k['table'].bind - return alter_column(self, *p, **k) - - def create(self, table=None, index_name=None, unique_name=None, - primary_key_name=None, populate_default=True, connection=None, **kwargs): - """Create this column in the database. - - Assumes the given table exists. ``ALTER TABLE ADD COLUMN``, - for most databases. - - :param table: Table instance to create on. - :param index_name: Creates :class:`ChangesetIndex` on this column. - :param unique_name: Creates :class:\ -`~migrate.changeset.constraint.UniqueConstraint` on this column. - :param primary_key_name: Creates :class:\ -`~migrate.changeset.constraint.PrimaryKeyConstraint` on this column. - :param populate_default: If True, created column will be \ -populated with defaults - :param connection: reuse connection instead of creating new one. - :type table: Table instance - :type index_name: string - :type unique_name: string - :type primary_key_name: string - :type populate_default: bool - :type connection: :class:`sqlalchemy.engine.base.Connection` instance - - :returns: self - """ - self.populate_default = populate_default - self.index_name = index_name - self.unique_name = unique_name - self.primary_key_name = primary_key_name - for cons in ('index_name', 'unique_name', 'primary_key_name'): - self._check_sanity_constraints(cons) - - self.add_to_table(table) - engine = self.table.bind - visitorcallable = get_engine_visitor(engine, 'columngenerator') - engine._run_visitor(visitorcallable, self, connection, **kwargs) - - # TODO: reuse existing connection - if self.populate_default and self.default is not None: - stmt = table.update().values({self: engine._execute_default(self.default)}) - engine.execute(stmt) - - return self - - def drop(self, table=None, connection=None, **kwargs): - """Drop this column from the database, leaving its table intact. - - ``ALTER TABLE DROP COLUMN``, for most databases. - - :param connection: reuse connection instead of creating new one. - :type connection: :class:`sqlalchemy.engine.base.Connection` instance - """ - if table is not None: - self.table = table - engine = self.table.bind - visitorcallable = get_engine_visitor(engine, 'columndropper') - engine._run_visitor(visitorcallable, self, connection, **kwargs) - self.remove_from_table(self.table, unset_table=False) - self.table = None - return self - - def add_to_table(self, table): - if table is not None and self.table is None: - if SQLA_07: - table.append_column(self) - else: - self._set_parent(table) - - def _col_name_in_constraint(self,cons,name): - return False - - def remove_from_table(self, table, unset_table=True): - # TODO: remove primary keys, constraints, etc - if unset_table: - self.table = None - - to_drop = set() - for index in table.indexes: - columns = [] - for col in index.columns: - if col.name!=self.name: - columns.append(col) - if columns: - index.columns=columns - else: - to_drop.add(index) - table.indexes = table.indexes - to_drop - - to_drop = set() - for cons in table.constraints: - # TODO: deal with other types of constraint - if isinstance(cons,(ForeignKeyConstraint, - UniqueConstraint)): - for col_name in cons.columns: - if not isinstance(col_name,basestring): - col_name = col_name.name - if self.name==col_name: - to_drop.add(cons) - table.constraints = table.constraints - to_drop - - if table.c.contains_column(self): - if SQLA_07: - table._columns.remove(self) - else: - table.c.remove(self) - - # TODO: this is fixed in 0.6 - def copy_fixed(self, **kw): - """Create a copy of this ``Column``, with all attributes.""" - return sqlalchemy.Column(self.name, self.type, self.default, - key=self.key, - primary_key=self.primary_key, - nullable=self.nullable, - quote=self.quote, - index=self.index, - unique=self.unique, - onupdate=self.onupdate, - autoincrement=self.autoincrement, - server_default=self.server_default, - server_onupdate=self.server_onupdate, - *[c.copy(**kw) for c in self.constraints]) - - def _check_sanity_constraints(self, name): - """Check if constraints names are correct""" - obj = getattr(self, name) - if (getattr(self, name[:-5]) and not obj): - raise InvalidConstraintError("Column.create() accepts index_name," - " primary_key_name and unique_name to generate constraints") - if not isinstance(obj, basestring) and obj is not None: - raise InvalidConstraintError( - "%s argument for column must be constraint name" % name) - - -class ChangesetIndex(object): - """Changeset extensions to SQLAlchemy Indexes.""" - - __visit_name__ = 'index' - - def rename(self, name, connection=None, **kwargs): - """Change the name of an index. - - :param name: New name of the Index. - :type name: string - :param connection: reuse connection instead of creating new one. - :type connection: :class:`sqlalchemy.engine.base.Connection` instance - """ - engine = self.table.bind - self.new_name = name - visitorcallable = get_engine_visitor(engine, 'schemachanger') - engine._run_visitor(visitorcallable, self, connection, **kwargs) - self.name = name - - -class ChangesetDefaultClause(object): - """Implements comparison between :class:`DefaultClause` instances""" - - def __eq__(self, other): - if isinstance(other, self.__class__): - if self.arg == other.arg: - return True - - def __ne__(self, other): - return not self.__eq__(other) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/exceptions.py --- a/kallithea/lib/dbmigrate/migrate/exceptions.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -""" - Provide exception classes for :mod:`migrate` -""" - - -class Error(Exception): - """Error base class.""" - - -class ApiError(Error): - """Base class for API errors.""" - - -class KnownError(ApiError): - """A known error condition.""" - - -class UsageError(ApiError): - """A known error condition where help should be displayed.""" - - -class ControlledSchemaError(Error): - """Base class for controlled schema errors.""" - - -class InvalidVersionError(ControlledSchemaError): - """Invalid version number.""" - - -class DatabaseNotControlledError(ControlledSchemaError): - """Database should be under version control, but it's not.""" - - -class DatabaseAlreadyControlledError(ControlledSchemaError): - """Database shouldn't be under version control, but it is""" - - -class WrongRepositoryError(ControlledSchemaError): - """This database is under version control by another repository.""" - - -class NoSuchTableError(ControlledSchemaError): - """The table does not exist.""" - - -class PathError(Error): - """Base class for path errors.""" - - -class PathNotFoundError(PathError): - """A path with no file was required; found a file.""" - - -class PathFoundError(PathError): - """A path with a file was required; found no file.""" - - -class RepositoryError(Error): - """Base class for repository errors.""" - - -class InvalidRepositoryError(RepositoryError): - """Invalid repository error.""" - - -class ScriptError(Error): - """Base class for script errors.""" - - -class InvalidScriptError(ScriptError): - """Invalid script error.""" - - -# migrate.changeset - -class NotSupportedError(Error): - """Not supported error""" - - -class InvalidConstraintError(Error): - """Invalid constraint error""" - -class MigrateDeprecationWarning(DeprecationWarning): - """Warning for deprecated features in Migrate""" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/__init__.py --- a/kallithea/lib/dbmigrate/migrate/versioning/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -""" - This package provides functionality to create and manage - repositories of database schema changesets and to apply these - changesets to databases. -""" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/api.py --- a/kallithea/lib/dbmigrate/migrate/versioning/api.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,384 +0,0 @@ -""" - This module provides an external API to the versioning system. - - .. versionchanged:: 0.6.0 - :func:`migrate.versioning.api.test` and schema diff functions - changed order of positional arguments so all accept `url` and `repository` - as first arguments. - - .. versionchanged:: 0.5.4 - ``--preview_sql`` displays source file when using SQL scripts. - If Python script is used, it runs the action with mocked engine and - returns captured SQL statements. - - .. versionchanged:: 0.5.4 - Deprecated ``--echo`` parameter in favour of new - :func:`migrate.versioning.util.construct_engine` behavior. -""" - -# Dear migrate developers, -# -# please do not comment this module using sphinx syntax because its -# docstrings are presented as user help and most users cannot -# interpret sphinx annotated ReStructuredText. -# -# Thanks, -# Jan Dittberner - -import sys -import inspect -import logging - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.versioning import repository, schema, version, \ - script as script_ # command name conflict -from kallithea.lib.dbmigrate.migrate.versioning.util import catch_known_errors, with_engine - - -log = logging.getLogger(__name__) -command_desc = { - 'help': 'displays help on a given command', - 'create': 'create an empty repository at the specified path', - 'script': 'create an empty change Python script', - 'script_sql': 'create empty change SQL scripts for given database', - 'version': 'display the latest version available in a repository', - 'db_version': 'show the current version of the repository under version control', - 'source': 'display the Python code for a particular version in this repository', - 'version_control': 'mark a database as under this repository\'s version control', - 'upgrade': 'upgrade a database to a later version', - 'downgrade': 'downgrade a database to an earlier version', - 'drop_version_control': 'removes version control from a database', - 'manage': 'creates a Python script that runs Migrate with a set of default values', - 'test': 'performs the upgrade and downgrade command on the given database', - 'compare_model_to_db': 'compare MetaData against the current database state', - 'create_model': 'dump the current database as a Python model to stdout', - 'make_update_script_for_model': 'create a script changing the old MetaData to the new (current) MetaData', - 'update_db_from_model': 'modify the database to match the structure of the current MetaData', -} -__all__ = command_desc.keys() - -Repository = repository.Repository -ControlledSchema = schema.ControlledSchema -VerNum = version.VerNum -PythonScript = script_.PythonScript -SqlScript = script_.SqlScript - - -# deprecated -def help(cmd=None, **opts): - """%prog help COMMAND - - Displays help on a given command. - """ - if cmd is None: - raise exceptions.UsageError(None) - try: - func = globals()[cmd] - except: - raise exceptions.UsageError( - "'%s' isn't a valid command. Try 'help COMMAND'" % cmd) - ret = func.__doc__ - if sys.argv[0]: - ret = ret.replace('%prog', sys.argv[0]) - return ret - -@catch_known_errors -def create(repository, name, **opts): - """%prog create REPOSITORY_PATH NAME [--table=TABLE] - - Create an empty repository at the specified path. - - You can specify the version_table to be used; by default, it is - 'migrate_version'. This table is created in all version-controlled - databases. - """ - repo_path = Repository.create(repository, name, **opts) - - -@catch_known_errors -def script(description, repository, **opts): - """%prog script DESCRIPTION REPOSITORY_PATH - - Create an empty change script using the next unused version number - appended with the given description. - - For instance, manage.py script "Add initial tables" creates: - repository/versions/001_Add_initial_tables.py - """ - repo = Repository(repository) - repo.create_script(description, **opts) - - -@catch_known_errors -def script_sql(database, description, repository, **opts): - """%prog script_sql DATABASE DESCRIPTION REPOSITORY_PATH - - Create empty change SQL scripts for given DATABASE, where DATABASE - is either specific ('postgresql', 'mysql', 'oracle', 'sqlite', etc.) - or generic ('default'). - - For instance, manage.py script_sql postgresql description creates: - repository/versions/001_description_postgresql_upgrade.sql and - repository/versions/001_description_postgresql_downgrade.sql - """ - repo = Repository(repository) - repo.create_script_sql(database, description, **opts) - - -def version(repository, **opts): - """%prog version REPOSITORY_PATH - - Display the latest version available in a repository. - """ - repo = Repository(repository) - return repo.latest - - -@with_engine -def db_version(url, repository, **opts): - """%prog db_version URL REPOSITORY_PATH - - Show the current version of the repository with the given - connection string, under version control of the specified - repository. - - The url should be any valid SQLAlchemy connection string. - """ - engine = opts.pop('engine') - schema = ControlledSchema(engine, repository) - return schema.version - - -def source(version, dest=None, repository=None, **opts): - """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH - - Display the Python code for a particular version in this - repository. Save it to the file at DESTINATION or, if omitted, - send to stdout. - """ - if repository is None: - raise exceptions.UsageError("A repository must be specified") - repo = Repository(repository) - ret = repo.version(version).script().source() - if dest is not None: - dest = open(dest, 'w') - dest.write(ret) - dest.close() - ret = None - return ret - - -def upgrade(url, repository, version=None, **opts): - """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql] - - Upgrade a database to a later version. - - This runs the upgrade() function defined in your change scripts. - - By default, the database is updated to the latest available - version. You may specify a version instead, if you wish. - - You may preview the Python or SQL code to be executed, rather than - actually executing it, using the appropriate 'preview' option. - """ - err = "Cannot upgrade a database of version %s to version %s. "\ - "Try 'downgrade' instead." - return _migrate(url, repository, version, upgrade=True, err=err, **opts) - - -def downgrade(url, repository, version, **opts): - """%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql] - - Downgrade a database to an earlier version. - - This is the reverse of upgrade; this runs the downgrade() function - defined in your change scripts. - - You may preview the Python or SQL code to be executed, rather than - actually executing it, using the appropriate 'preview' option. - """ - err = "Cannot downgrade a database of version %s to version %s. "\ - "Try 'upgrade' instead." - return _migrate(url, repository, version, upgrade=False, err=err, **opts) - -@with_engine -def test(url, repository, **opts): - """%prog test URL REPOSITORY_PATH [VERSION] - - Performs the upgrade and downgrade option on the given - database. This is not a real test and may leave the database in a - bad state. You should therefore better run the test on a copy of - your database. - """ - engine = opts.pop('engine') - repos = Repository(repository) - - # Upgrade - log.info("Upgrading...") - script = repos.version(None).script(engine.name, 'upgrade') - script.run(engine, 1) - log.info("done") - - log.info("Downgrading...") - script = repos.version(None).script(engine.name, 'downgrade') - script.run(engine, -1) - log.info("done") - log.info("Success") - - -@with_engine -def version_control(url, repository, version=None, **opts): - """%prog version_control URL REPOSITORY_PATH [VERSION] - - Mark a database as under this repository's version control. - - Once a database is under version control, schema changes should - only be done via change scripts in this repository. - - This creates the table version_table in the database. - - The url should be any valid SQLAlchemy connection string. - - By default, the database begins at version 0 and is assumed to be - empty. If the database is not empty, you may specify a version at - which to begin instead. No attempt is made to verify this - version's correctness - the database schema is expected to be - identical to what it would be if the database were created from - scratch. - """ - engine = opts.pop('engine') - ControlledSchema.create(engine, repository, version) - - -@with_engine -def drop_version_control(url, repository, **opts): - """%prog drop_version_control URL REPOSITORY_PATH - - Removes version control from a database. - """ - engine = opts.pop('engine') - schema = ControlledSchema(engine, repository) - schema.drop() - - -def manage(file, **opts): - """%prog manage FILENAME [VARIABLES...] - - Creates a script that runs Migrate with a set of default values. - - For example:: - - %prog manage manage.py --repository=/path/to/repository \ ---url=sqlite:///project.db - - would create the script manage.py. The following two commands - would then have exactly the same results:: - - python manage.py version - %prog version --repository=/path/to/repository - """ - Repository.create_manage_file(file, **opts) - - -@with_engine -def compare_model_to_db(url, repository, model, **opts): - """%prog compare_model_to_db URL REPOSITORY_PATH MODEL - - Compare the current model (assumed to be a module level variable - of type sqlalchemy.MetaData) against the current database. - - NOTE: This is EXPERIMENTAL. - """ # TODO: get rid of EXPERIMENTAL label - engine = opts.pop('engine') - return ControlledSchema.compare_model_to_db(engine, model, repository) - - -@with_engine -def create_model(url, repository, **opts): - """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True] - - Dump the current database as a Python model to stdout. - - NOTE: This is EXPERIMENTAL. - """ # TODO: get rid of EXPERIMENTAL label - engine = opts.pop('engine') - declarative = opts.get('declarative', False) - return ControlledSchema.create_model(engine, repository, declarative) - - -@catch_known_errors -@with_engine -def make_update_script_for_model(url, repository, oldmodel, model, **opts): - """%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH - - Create a script changing the old Python model to the new (current) - Python model, sending to stdout. - - NOTE: This is EXPERIMENTAL. - """ # TODO: get rid of EXPERIMENTAL label - engine = opts.pop('engine') - return PythonScript.make_update_script_for_model( - engine, oldmodel, model, repository, **opts) - - -@with_engine -def update_db_from_model(url, repository, model, **opts): - """%prog update_db_from_model URL REPOSITORY_PATH MODEL - - Modify the database to match the structure of the current Python - model. This also sets the db_version number to the latest in the - repository. - - NOTE: This is EXPERIMENTAL. - """ # TODO: get rid of EXPERIMENTAL label - engine = opts.pop('engine') - schema = ControlledSchema(engine, repository) - schema.update_db_from_model(model) - -@with_engine -def _migrate(url, repository, version, upgrade, err, **opts): - engine = opts.pop('engine') - url = str(engine.url) - schema = ControlledSchema(engine, repository) - version = _migrate_version(schema, version, upgrade, err) - - changeset = schema.changeset(version) - for ver, change in changeset: - nextver = ver + changeset.step - log.info('%s -> %s... ', ver, nextver) - - if opts.get('preview_sql'): - if isinstance(change, PythonScript): - log.info(change.preview_sql(url, changeset.step, **opts)) - elif isinstance(change, SqlScript): - log.info(change.source()) - - elif opts.get('preview_py'): - if not isinstance(change, PythonScript): - raise exceptions.UsageError("Python source can be only displayed" - " for python migration files") - source_ver = max(ver, nextver) - module = schema.repository.version(source_ver).script().module - funcname = upgrade and "upgrade" or "downgrade" - func = getattr(module, funcname) - log.info(inspect.getsource(func)) - else: - schema.runchange(ver, change, changeset.step) - log.info('done') - - -def _migrate_version(schema, version, upgrade, err): - if version is None: - return version - # Version is specified: ensure we're upgrading in the right direction - # (current version < target version for upgrading; reverse for down) - version = VerNum(version) - cur = schema.version - if upgrade is not None: - if upgrade: - direction = cur <= version - else: - direction = cur >= version - if not direction: - raise exceptions.KnownError(err % (cur, version)) - return version diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/cfgparse.py --- a/kallithea/lib/dbmigrate/migrate/versioning/cfgparse.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -""" - Configuration parser module. -""" - -from ConfigParser import ConfigParser - -from kallithea.lib.dbmigrate.migrate.versioning.config import * -from kallithea.lib.dbmigrate.migrate.versioning import pathed - - -class Parser(ConfigParser): - """A project configuration file.""" - - def to_dict(self, sections=None): - """It's easier to access config values like dictionaries""" - return self._sections - - -class Config(pathed.Pathed, Parser): - """Configuration class.""" - - def __init__(self, path, *p, **k): - """Confirm the config file exists; read it.""" - self.require_found(path) - pathed.Pathed.__init__(self, path) - Parser.__init__(self, *p, **k) - self.read(path) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/config.py --- a/kallithea/lib/dbmigrate/migrate/versioning/config.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -from sqlalchemy.util import OrderedDict - - -__all__ = ['databases', 'operations'] - -databases = ('sqlite', 'postgres', 'mysql', 'oracle', 'mssql', 'firebird') - -# Map operation names to function names -operations = OrderedDict() -operations['upgrade'] = 'upgrade' -operations['downgrade'] = 'downgrade' diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/genmodel.py --- a/kallithea/lib/dbmigrate/migrate/versioning/genmodel.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,284 +0,0 @@ -""" -Code to generate a Python model from a database or differences -between a model and database. - -Some of this is borrowed heavily from the AutoCode project at: -http://code.google.com/p/sqlautocode/ -""" - -import sys -import logging - -import sqlalchemy - -from kallithea.lib.dbmigrate import migrate -from kallithea.lib.dbmigrate.migrate import changeset - - -log = logging.getLogger(__name__) -HEADER = """ -## File autogenerated by genmodel.py - -from sqlalchemy import * -meta = MetaData() -""" - -DECLARATIVE_HEADER = """ -## File autogenerated by genmodel.py - -from sqlalchemy import * -from sqlalchemy.ext import declarative - -Base = declarative.declarative_base() -""" - - -class ModelGenerator(object): - """Various transformations from an A, B diff. - - In the implementation, A tends to be called the model and B - the database (although this is not true of all diffs). - The diff is directionless, but transformations apply the diff - in a particular direction, described in the method name. - """ - - def __init__(self, diff, engine, declarative=False): - self.diff = diff - self.engine = engine - self.declarative = declarative - - def column_repr(self, col): - kwarg = [] - if col.key != col.name: - kwarg.append('key') - if col.primary_key: - col.primary_key = True # otherwise it dumps it as 1 - kwarg.append('primary_key') - if not col.nullable: - kwarg.append('nullable') - if col.onupdate: - kwarg.append('onupdate') - if col.default: - if col.primary_key: - # I found that PostgreSQL automatically creates a - # default value for the sequence, but let's not show - # that. - pass - else: - kwarg.append('default') - args = ['%s=%r' % (k, getattr(col, k)) for k in kwarg] - - # crs: not sure if this is good idea, but it gets rid of extra - # u'' - name = col.name.encode('utf8') - - type_ = col.type - for cls in col.type.__class__.__mro__: - if cls.__module__ == 'sqlalchemy.types' and \ - not cls.__name__.isupper(): - if cls is not type_.__class__: - type_ = cls() - break - - type_repr = repr(type_) - if type_repr.endswith('()'): - type_repr = type_repr[:-2] - - constraints = [repr(cn) for cn in col.constraints] - - data = { - 'name': name, - 'commonStuff': ', '.join([type_repr] + constraints + args), - } - - if self.declarative: - return """%(name)s = Column(%(commonStuff)s)""" % data - else: - return """Column(%(name)r, %(commonStuff)s)""" % data - - def _getTableDefn(self, table, metaName='meta'): - out = [] - tableName = table.name - if self.declarative: - out.append("class %(table)s(Base):" % {'table': tableName}) - out.append(" __tablename__ = '%(table)s'\n" % - {'table': tableName}) - for col in table.columns: - out.append(" %s" % self.column_repr(col)) - out.append('\n') - else: - out.append("%(table)s = Table('%(table)s', %(meta)s," % - {'table': tableName, 'meta': metaName}) - for col in table.columns: - out.append(" %s," % self.column_repr(col)) - out.append(")\n") - return out - - def _get_tables(self,missingA=False,missingB=False,modified=False): - to_process = [] - for bool_,names,metadata in ( - (missingA,self.diff.tables_missing_from_A,self.diff.metadataB), - (missingB,self.diff.tables_missing_from_B,self.diff.metadataA), - (modified,self.diff.tables_different,self.diff.metadataA), - ): - if bool_: - for name in names: - yield metadata.tables.get(name) - - def genBDefinition(self): - """Generates the source code for a definition of B. - - Assumes a diff where A is empty. - - Was: toPython. Assume database (B) is current and model (A) is empty. - """ - - out = [] - if self.declarative: - out.append(DECLARATIVE_HEADER) - else: - out.append(HEADER) - out.append("") - for table in self._get_tables(missingA=True): - out.extend(self._getTableDefn(table)) - return '\n'.join(out) - - def genB2AMigration(self, indent=' '): - """Generate a migration from B to A. - - Was: toUpgradeDowngradePython - Assume model (A) is most current and database (B) is out-of-date. - """ - - decls = ['from migrate.changeset import schema', - 'pre_meta = MetaData()', - 'post_meta = MetaData()', - ] - upgradeCommands = ['pre_meta.bind = migrate_engine', - 'post_meta.bind = migrate_engine'] - downgradeCommands = list(upgradeCommands) - - for tn in self.diff.tables_missing_from_A: - pre_table = self.diff.metadataB.tables[tn] - decls.extend(self._getTableDefn(pre_table, metaName='pre_meta')) - upgradeCommands.append( - "pre_meta.tables[%(table)r].drop()" % {'table': tn}) - downgradeCommands.append( - "pre_meta.tables[%(table)r].create()" % {'table': tn}) - - for tn in self.diff.tables_missing_from_B: - post_table = self.diff.metadataA.tables[tn] - decls.extend(self._getTableDefn(post_table, metaName='post_meta')) - upgradeCommands.append( - "post_meta.tables[%(table)r].create()" % {'table': tn}) - downgradeCommands.append( - "post_meta.tables[%(table)r].drop()" % {'table': tn}) - - for (tn, td) in self.diff.tables_different.iteritems(): - if td.columns_missing_from_A or td.columns_different: - pre_table = self.diff.metadataB.tables[tn] - decls.extend(self._getTableDefn( - pre_table, metaName='pre_meta')) - if td.columns_missing_from_B or td.columns_different: - post_table = self.diff.metadataA.tables[tn] - decls.extend(self._getTableDefn( - post_table, metaName='post_meta')) - - for col in td.columns_missing_from_A: - upgradeCommands.append( - 'pre_meta.tables[%r].columns[%r].drop()' % (tn, col)) - downgradeCommands.append( - 'pre_meta.tables[%r].columns[%r].create()' % (tn, col)) - for col in td.columns_missing_from_B: - upgradeCommands.append( - 'post_meta.tables[%r].columns[%r].create()' % (tn, col)) - downgradeCommands.append( - 'post_meta.tables[%r].columns[%r].drop()' % (tn, col)) - for modelCol, databaseCol, modelDecl, databaseDecl in td.columns_different: - upgradeCommands.append( - 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( - tn, modelCol.name, databaseCol.name)) - downgradeCommands.append( - 'assert False, "Can\'t alter columns: %s:%s=>%s"' % ( - tn, modelCol.name, databaseCol.name)) - - return ( - '\n'.join(decls), - '\n'.join('%s%s' % (indent, line) for line in upgradeCommands), - '\n'.join('%s%s' % (indent, line) for line in downgradeCommands)) - - def _db_can_handle_this_change(self,td): - """Check if the database can handle going from B to A.""" - - if (td.columns_missing_from_B - and not td.columns_missing_from_A - and not td.columns_different): - # Even sqlite can handle column additions. - return True - else: - return not self.engine.url.drivername.startswith('sqlite') - - def runB2A(self): - """Goes from B to A. - - Was: applyModel. Apply model (A) to current database (B). - """ - - meta = sqlalchemy.MetaData(self.engine) - - for table in self._get_tables(missingA=True): - table = table.tometadata(meta) - table.drop() - for table in self._get_tables(missingB=True): - table = table.tometadata(meta) - table.create() - for modelTable in self._get_tables(modified=True): - tableName = modelTable.name - modelTable = modelTable.tometadata(meta) - dbTable = self.diff.metadataB.tables[tableName] - - td = self.diff.tables_different[tableName] - - if self._db_can_handle_this_change(td): - - for col in td.columns_missing_from_B: - modelTable.columns[col].create() - for col in td.columns_missing_from_A: - dbTable.columns[col].drop() - # XXX handle column changes here. - else: - # Sqlite doesn't support drop column, so you have to - # do more: create temp table, copy data to it, drop - # old table, create new table, copy data back. - # - # I wonder if this is guaranteed to be unique? - tempName = '_temp_%s' % modelTable.name - - def getCopyStatement(): - preparer = self.engine.dialect.preparer - commonCols = [] - for modelCol in modelTable.columns: - if modelCol.name in dbTable.columns: - commonCols.append(modelCol.name) - commonColsStr = ', '.join(commonCols) - return 'INSERT INTO %s (%s) SELECT %s FROM %s' % \ - (tableName, commonColsStr, commonColsStr, tempName) - - # Move the data in one transaction, so that we don't - # leave the database in a nasty state. - connection = self.engine.connect() - trans = connection.begin() - try: - connection.execute( - 'CREATE TEMPORARY TABLE %s as SELECT * from %s' % \ - (tempName, modelTable.name)) - # make sure the drop takes place inside our - # transaction with the bind parameter - modelTable.drop(bind=connection) - modelTable.create(bind=connection) - connection.execute(getCopyStatement()) - connection.execute('DROP TABLE %s' % tempName) - trans.commit() - except: - trans.rollback() - raise diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/migrate_repository.py --- a/kallithea/lib/dbmigrate/migrate/versioning/migrate_repository.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -""" - Script to migrate repository from sqlalchemy <= 0.4.4 to the new - repository schema. This shouldn't use any other migrate modules, so - that it can work in any version. -""" - -import os -import sys -import logging - -log = logging.getLogger(__name__) - - -def usage(): - """Gives usage information.""" - print """Usage: %(prog)s repository-to-migrate - - Upgrade your repository to the new flat format. - - NOTE: You should probably make a backup before running this. - """ % {'prog': sys.argv[0]} - - sys.exit(1) - - -def delete_file(filepath): - """Deletes a file and prints a message.""" - log.info('Deleting file: %s', filepath) - os.remove(filepath) - - -def move_file(src, tgt): - """Moves a file and prints a message.""" - log.info('Moving file %s to %s', src, tgt) - if os.path.exists(tgt): - raise Exception( - 'Cannot move file %s because target %s already exists' % \ - (src, tgt)) - os.rename(src, tgt) - - -def delete_directory(dirpath): - """Delete a directory and print a message.""" - log.info('Deleting directory: %s', dirpath) - os.rmdir(dirpath) - - -def migrate_repository(repos): - """Does the actual migration to the new repository format.""" - log.info('Migrating repository at: %s to new format', repos) - versions = '%s/versions' % repos - dirs = os.listdir(versions) - # Only use int's in list. - numdirs = [int(dirname) for dirname in dirs if dirname.isdigit()] - numdirs.sort() # Sort list. - for dirname in numdirs: - origdir = '%s/%s' % (versions, dirname) - log.info('Working on directory: %s', origdir) - files = os.listdir(origdir) - files.sort() - for filename in files: - # Delete compiled Python files. - if filename.endswith('.pyc') or filename.endswith('.pyo'): - delete_file('%s/%s' % (origdir, filename)) - - # Delete empty __init__.py files. - origfile = '%s/__init__.py' % origdir - if os.path.exists(origfile) and len(open(origfile).read()) == 0: - delete_file(origfile) - - # Move sql upgrade scripts. - if filename.endswith('.sql'): - version, dbms, operation = filename.split('.', 3)[0:3] - origfile = '%s/%s' % (origdir, filename) - # For instance: 2.postgres.upgrade.sql -> - # 002_postgres_upgrade.sql - tgtfile = '%s/%03d_%s_%s.sql' % ( - versions, int(version), dbms, operation) - move_file(origfile, tgtfile) - - # Move Python upgrade script. - pyfile = '%s.py' % dirname - pyfilepath = '%s/%s' % (origdir, pyfile) - if os.path.exists(pyfilepath): - tgtfile = '%s/%03d.py' % (versions, int(dirname)) - move_file(pyfilepath, tgtfile) - - # Try to remove directory. Will fail if it's not empty. - delete_directory(origdir) - - -def main(): - """Main function to be called when using this script.""" - if len(sys.argv) != 2: - usage() - migrate_repository(sys.argv[1]) - - -if __name__ == '__main__': - main() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/pathed.py --- a/kallithea/lib/dbmigrate/migrate/versioning/pathed.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -""" - A path/directory class. -""" - -import os -import shutil -import logging - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.versioning.config import * -from kallithea.lib.dbmigrate.migrate.versioning.util import KeyedInstance - - -log = logging.getLogger(__name__) - -class Pathed(KeyedInstance): - """ - A class associated with a path/directory tree. - - Only one instance of this class may exist for a particular file; - __new__ will return an existing instance if possible - """ - parent = None - - @classmethod - def _key(cls, path): - return str(path) - - def __init__(self, path): - self.path = path - if self.__class__.parent is not None: - self._init_parent(path) - - def _init_parent(self, path): - """Try to initialize this object's parent, if it has one""" - parent_path = self.__class__._parent_path(path) - self.parent = self.__class__.parent(parent_path) - log.debug("Getting parent %r:%r", self.__class__.parent, parent_path) - self.parent._init_child(path, self) - - def _init_child(self, child, path): - """Run when a child of this object is initialized. - - Parameters: the child object; the path to this object (its - parent) - """ - - @classmethod - def _parent_path(cls, path): - """ - Fetch the path of this object's parent from this object's path. - """ - # os.path.dirname(), but strip directories like files (like - # unix basename) - # - # Treat directories like files... - if path[-1] == '/': - path = path[:-1] - ret = os.path.dirname(path) - return ret - - @classmethod - def require_notfound(cls, path): - """Ensures a given path does not already exist""" - if os.path.exists(path): - raise exceptions.PathFoundError(path) - - @classmethod - def require_found(cls, path): - """Ensures a given path already exists""" - if not os.path.exists(path): - raise exceptions.PathNotFoundError(path) - - def __str__(self): - return self.path diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/repository.py --- a/kallithea/lib/dbmigrate/migrate/versioning/repository.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,247 +0,0 @@ -""" - SQLAlchemy migrate repository management. -""" -import os -import shutil -import string -import logging - -from pkg_resources import resource_filename -from tempita import Template as TempitaTemplate - -import kallithea -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.versioning import version, pathed, cfgparse -from kallithea.lib.dbmigrate.migrate.versioning.template import Template -from kallithea.lib.dbmigrate.migrate.versioning.config import * - - -log = logging.getLogger(__name__) - -class Changeset(dict): - """A collection of changes to be applied to a database. - - Changesets are bound to a repository and manage a set of - scripts from that repository. - - Behaves like a dict, for the most part. Keys are ordered based on step value. - """ - - def __init__(self, start, *changes, **k): - """ - Give a start version; step must be explicitly stated. - """ - self.step = k.pop('step', 1) - self.start = version.VerNum(start) - self.end = self.start - for change in changes: - self.add(change) - - def __iter__(self): - return iter(self.items()) - - def keys(self): - """ - In a series of upgrades x -> y, keys are version x. Sorted. - """ - ret = super(Changeset, self).keys() - # Reverse order if downgrading - ret.sort(reverse=(self.step < 1)) - return ret - - def values(self): - return [self[k] for k in self.keys()] - - def items(self): - return zip(self.keys(), self.values()) - - def add(self, change): - """Add new change to changeset""" - key = self.end - self.end += self.step - self[key] = change - - def run(self, *p, **k): - """Run the changeset scripts""" - for _version, script in self: - script.run(*p, **k) - - -class Repository(pathed.Pathed): - """A project's change script repository""" - - _config = 'migrate.cfg' - _versions = 'versions' - - def __init__(self, path): - log.debug('Loading repository %s...', path) - self.verify(path) - super(Repository, self).__init__(path) - self.config = cfgparse.Config(os.path.join(self.path, self._config)) - self.versions = version.Collection(os.path.join(self.path, - self._versions)) - log.debug('Repository %s loaded successfully', path) - log.debug('Config: %r', self.config.to_dict()) - - @classmethod - def verify(cls, path): - """ - Ensure the target path is a valid repository. - - :raises: :exc:`InvalidRepositoryError ` - """ - # Ensure the existence of required files - try: - cls.require_found(path) - cls.require_found(os.path.join(path, cls._config)) - cls.require_found(os.path.join(path, cls._versions)) - except exceptions.PathNotFoundError as e: - raise exceptions.InvalidRepositoryError(path) - - @classmethod - def prepare_config(cls, tmpl_dir, name, options=None): - """ - Prepare a project configuration file for a new project. - - :param tmpl_dir: Path to Repository template - :param config_file: Name of the config file in Repository template - :param name: Repository name - :type tmpl_dir: string - :type config_file: string - :type name: string - :returns: Populated config file - """ - if options is None: - options = {} - options.setdefault('version_table', 'migrate_version') - options.setdefault('repository_id', name) - options.setdefault('required_dbs', []) - options.setdefault('use_timestamp_numbering', False) - - tmpl = open(os.path.join(tmpl_dir, cls._config)).read() - ret = TempitaTemplate(tmpl).substitute(options) - - # cleanup - del options['__template_name__'] - - return ret - - @classmethod - def create(cls, path, name, **opts): - """Create a repository at a specified path""" - cls.require_notfound(path) - theme = opts.pop('templates_theme', None) - t_path = opts.pop('templates_path', None) - - # Create repository - tmpl_dir = Template(t_path).get_repository(theme=theme) - shutil.copytree(tmpl_dir, path) - - # Edit config defaults - config_text = cls.prepare_config(tmpl_dir, name, options=opts) - fd = open(os.path.join(path, cls._config), 'w') - fd.write(config_text) - fd.close() - - opts['repository_name'] = name - - # Create a management script - manager = os.path.join(path, 'manage.py') - Repository.create_manage_file(manager, templates_theme=theme, - templates_path=t_path, **opts) - - return cls(path) - - def create_script(self, description, **k): - """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`""" - - k['use_timestamp_numbering'] = self.use_timestamp_numbering - self.versions.create_new_python_version(description, **k) - - def create_script_sql(self, database, description, **k): - """API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`""" - k['use_timestamp_numbering'] = self.use_timestamp_numbering - self.versions.create_new_sql_version(database, description, **k) - - @property - def latest(self): - """API to :attr:`migrate.versioning.version.Collection.latest`""" - return self.versions.latest - - @property - def version_table(self): - """Returns version_table name specified in config""" - return self.config.get('db_settings', 'version_table') - - @property - def id(self): - """Returns repository id specified in config""" - # Adjust the value read from kallithea/lib/dbmigrate/migrate.cfg, normally "kallithea_db_migrations" - s = self.config.get('db_settings', 'repository_id') - if s == "kallithea_db_migrations": - s = kallithea.DB_MIGRATIONS - return s - - @property - def use_timestamp_numbering(self): - """Returns use_timestamp_numbering specified in config""" - if self.config.has_option('db_settings', 'use_timestamp_numbering'): - return self.config.getboolean('db_settings', 'use_timestamp_numbering') - return False - - def version(self, *p, **k): - """API to :attr:`migrate.versioning.version.Collection.version`""" - return self.versions.version(*p, **k) - - @classmethod - def clear(cls): - # TODO: deletes repo - super(Repository, cls).clear() - version.Collection.clear() - - def changeset(self, database, start, end=None): - """Create a changeset to migrate this database from ver. start to end/latest. - - :param database: name of database to generate changeset - :param start: version to start at - :param end: version to end at (latest if None given) - :type database: string - :type start: int - :type end: int - :returns: :class:`Changeset instance ` - """ - start = version.VerNum(start) - - if end is None: - end = self.latest - else: - end = version.VerNum(end) - - if start <= end: - step = 1 - range_mod = 1 - op = 'upgrade' - else: - step = -1 - range_mod = 0 - op = 'downgrade' - - versions = range(start + range_mod, end + range_mod, step) - changes = [self.version(v).script(database, op) for v in versions] - ret = Changeset(start, step=step, *changes) - return ret - - @classmethod - def create_manage_file(cls, file_, **opts): - """Create a project management script (manage.py) - - :param file_: Destination file to be written - :param opts: Options that are passed to :func:`migrate.versioning.shell.main` - """ - mng_file = Template(opts.pop('templates_path', None))\ - .get_manage(theme=opts.pop('templates_theme', None)) - - tmpl = open(mng_file).read() - fd = open(file_, 'w') - fd.write(TempitaTemplate(tmpl).substitute(opts)) - fd.close() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/schema.py --- a/kallithea/lib/dbmigrate/migrate/versioning/schema.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,221 +0,0 @@ -""" - Database schema version management. -""" -import sys -import logging - -from sqlalchemy import (Table, Column, MetaData, String, Text, Integer, - create_engine) -from sqlalchemy.sql import and_ -from sqlalchemy import exc as sa_exceptions -from sqlalchemy.sql import bindparam - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.changeset import SQLA_07 -from kallithea.lib.dbmigrate.migrate.versioning import genmodel, schemadiff -from kallithea.lib.dbmigrate.migrate.versioning.repository import Repository -from kallithea.lib.dbmigrate.migrate.versioning.util import load_model -from kallithea.lib.dbmigrate.migrate.versioning.version import VerNum - - -log = logging.getLogger(__name__) - - -class ControlledSchema(object): - """A database under version control""" - - def __init__(self, engine, repository): - if isinstance(repository, basestring): - repository = Repository(repository) - self.engine = engine - self.repository = repository - self.meta = MetaData(engine) - self.load() - - def __eq__(self, other): - """Compare two schemas by repositories and versions""" - return (self.repository is other.repository \ - and self.version == other.version) - - def load(self): - """Load controlled schema version info from DB""" - tname = self.repository.version_table - try: - if not hasattr(self, 'table') or self.table is None: - self.table = Table(tname, self.meta, autoload=True) - - result = self.engine.execute(self.table.select( - self.table.c.repository_id == str(self.repository.id))) - - data = list(result)[0] - except: - cls, exc, tb = sys.exc_info() - raise exceptions.DatabaseNotControlledError, exc.__str__(), tb - - self.version = data['version'] - return data - - def drop(self): - """ - Remove version control from a database. - """ - if SQLA_07: - try: - self.table.drop() - except sa_exceptions.DatabaseError: - raise exceptions.DatabaseNotControlledError(str(self.table)) - else: - try: - self.table.drop() - except (sa_exceptions.SQLError): - raise exceptions.DatabaseNotControlledError(str(self.table)) - - def changeset(self, version=None): - """API to Changeset creation. - - Uses self.version for start version and engine.name - to get database name. - """ - database = self.engine.name - start_ver = self.version - changeset = self.repository.changeset(database, start_ver, version) - return changeset - - def runchange(self, ver, change, step): - startver = ver - endver = ver + step - # Current database version must be correct! Don't run if corrupt! - if self.version != startver: - raise exceptions.InvalidVersionError("%s is not %s" % \ - (self.version, startver)) - # Run the change - change.run(self.engine, step) - - # Update/refresh database version - self.update_repository_table(startver, endver) - self.load() - - def update_repository_table(self, startver, endver): - """Update version_table with new information""" - update = self.table.update(and_(self.table.c.version == int(startver), - self.table.c.repository_id == str(self.repository.id))) - self.engine.execute(update, version=int(endver)) - - def upgrade(self, version=None): - """ - Upgrade (or downgrade) to a specified version, or latest version. - """ - changeset = self.changeset(version) - for ver, change in changeset: - self.runchange(ver, change, changeset.step) - - def update_db_from_model(self, model): - """ - Modify the database to match the structure of the current Python model. - """ - model = load_model(model) - - diff = schemadiff.getDiffOfModelAgainstDatabase( - model, self.engine, excludeTables=[self.repository.version_table] - ) - genmodel.ModelGenerator(diff,self.engine).runB2A() - - self.update_repository_table(self.version, int(self.repository.latest)) - - self.load() - - @classmethod - def create(cls, engine, repository, version=None): - """ - Declare a database to be under a repository's version control. - - :raises: :exc:`DatabaseAlreadyControlledError` - :returns: :class:`ControlledSchema` - """ - # Confirm that the version # is valid: positive, integer, - # exists in repos - if isinstance(repository, basestring): - repository = Repository(repository) - version = cls._validate_version(repository, version) - table = cls._create_table_version(engine, repository, version) - # TODO: history table - # Load repository information and return - return cls(engine, repository) - - @classmethod - def _validate_version(cls, repository, version): - """ - Ensures this is a valid version number for this repository. - - :raises: :exc:`InvalidVersionError` if invalid - :return: valid version number - """ - if version is None: - version = 0 - try: - version = VerNum(version) # raises valueerror - if version < 0 or version > repository.latest: - raise ValueError() - except ValueError: - raise exceptions.InvalidVersionError(version) - return version - - @classmethod - def _create_table_version(cls, engine, repository, version): - """ - Creates the versioning table in a database. - - :raises: :exc:`DatabaseAlreadyControlledError` - """ - # Create tables - tname = repository.version_table - meta = MetaData(engine) - - table = Table( - tname, meta, - Column('repository_id', String(250), primary_key=True), - Column('repository_path', Text), - Column('version', Integer), ) - - # there can be multiple repositories/schemas in the same db - if not table.exists(): - table.create() - - # test for existing repository_id - s = table.select(table.c.repository_id == bindparam("repository_id")) - result = engine.execute(s, repository_id=repository.id) - if result.fetchone(): - raise exceptions.DatabaseAlreadyControlledError - - # Insert data - engine.execute(table.insert().values( - repository_id=repository.id, - repository_path=repository.path, - version=int(version))) - return table - - @classmethod - def compare_model_to_db(cls, engine, model, repository): - """ - Compare the current model against the current database. - """ - if isinstance(repository, basestring): - repository = Repository(repository) - model = load_model(model) - - diff = schemadiff.getDiffOfModelAgainstDatabase( - model, engine, excludeTables=[repository.version_table]) - return diff - - @classmethod - def create_model(cls, engine, repository, declarative=False): - """ - Dump the current database as a Python model. - """ - if isinstance(repository, basestring): - repository = Repository(repository) - - diff = schemadiff.getDiffOfModelAgainstDatabase( - MetaData(), engine, excludeTables=[repository.version_table] - ) - return genmodel.ModelGenerator(diff, engine, declarative).genBDefinition() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/schemadiff.py --- a/kallithea/lib/dbmigrate/migrate/versioning/schemadiff.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,295 +0,0 @@ -""" - Schema differencing support. -""" - -import logging -import sqlalchemy - -from kallithea.lib.dbmigrate.migrate.changeset import SQLA_06 -from sqlalchemy.types import Float - -log = logging.getLogger(__name__) - - -def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None): - """ - Return differences of model against database. - - :return: object which will evaluate to :keyword:`True` if there \ - are differences else :keyword:`False`. - """ - db_metadata = sqlalchemy.MetaData(engine) - db_metadata.reflect() - - # sqlite will include a dynamically generated 'sqlite_sequence' table if - # there are autoincrement sequences in the database; this should not be - # compared. - if engine.dialect.name == 'sqlite': - if 'sqlite_sequence' in db_metadata.tables: - db_metadata.remove(db_metadata.tables['sqlite_sequence']) - - return SchemaDiff(metadata, db_metadata, - labelA='model', - labelB='database', - excludeTables=excludeTables) - - -def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None): - """ - Return differences of model against another model. - - :return: object which will evaluate to :keyword:`True` if there \ - are differences else :keyword:`False`. - """ - return SchemaDiff(metadataA, metadataB, excludeTables=excludeTables) - - -class ColDiff(object): - """ - Container for differences in one :class:`~sqlalchemy.schema.Column` - between two :class:`~sqlalchemy.schema.Table` instances, ``A`` - and ``B``. - - .. attribute:: col_A - - The :class:`~sqlalchemy.schema.Column` object for A. - - .. attribute:: col_B - - The :class:`~sqlalchemy.schema.Column` object for B. - - .. attribute:: type_A - - The most generic type of the :class:`~sqlalchemy.schema.Column` - object in A. - - .. attribute:: type_B - - The most generic type of the :class:`~sqlalchemy.schema.Column` - object in A. - - """ - - diff = False - - def __init__(self,col_A,col_B): - self.col_A = col_A - self.col_B = col_B - - self.type_A = col_A.type - self.type_B = col_B.type - - self.affinity_A = self.type_A._type_affinity - self.affinity_B = self.type_B._type_affinity - - if self.affinity_A is not self.affinity_B: - self.diff = True - return - - if isinstance(self.type_A,Float) or isinstance(self.type_B,Float): - if not (isinstance(self.type_A,Float) and isinstance(self.type_B,Float)): - self.diff=True - return - - for attr in ('precision','scale','length'): - A = getattr(self.type_A,attr,None) - B = getattr(self.type_B,attr,None) - if not (A is None or B is None) and A!=B: - self.diff=True - return - - def __nonzero__(self): - return self.diff - -class TableDiff(object): - """ - Container for differences in one :class:`~sqlalchemy.schema.Table` - between two :class:`~sqlalchemy.schema.MetaData` instances, ``A`` - and ``B``. - - .. attribute:: columns_missing_from_A - - A sequence of column names that were found in B but weren't in - A. - - .. attribute:: columns_missing_from_B - - A sequence of column names that were found in A but weren't in - B. - - .. attribute:: columns_different - - A dictionary containing information about columns that were - found to be different. - It maps column names to a :class:`ColDiff` objects describing the - differences found. - """ - __slots__ = ( - 'columns_missing_from_A', - 'columns_missing_from_B', - 'columns_different', - ) - - def __nonzero__(self): - return bool( - self.columns_missing_from_A or - self.columns_missing_from_B or - self.columns_different - ) - -class SchemaDiff(object): - """ - Compute the difference between two :class:`~sqlalchemy.schema.MetaData` - objects. - - The string representation of a :class:`SchemaDiff` will summarise - the changes found between the two - :class:`~sqlalchemy.schema.MetaData` objects. - - The length of a :class:`SchemaDiff` will give the number of - changes found, enabling it to be used much like a boolean in - expressions. - - :param metadataA: - First :class:`~sqlalchemy.schema.MetaData` to compare. - - :param metadataB: - Second :class:`~sqlalchemy.schema.MetaData` to compare. - - :param labelA: - The label to use in messages about the first - :class:`~sqlalchemy.schema.MetaData`. - - :param labelB: - The label to use in messages about the second - :class:`~sqlalchemy.schema.MetaData`. - - :param excludeTables: - A sequence of table names to exclude. - - .. attribute:: tables_missing_from_A - - A sequence of table names that were found in B but weren't in - A. - - .. attribute:: tables_missing_from_B - - A sequence of table names that were found in A but weren't in - B. - - .. attribute:: tables_different - - A dictionary containing information about tables that were found - to be different. - It maps table names to a :class:`TableDiff` objects describing the - differences found. - """ - - def __init__(self, - metadataA, metadataB, - labelA='metadataA', - labelB='metadataB', - excludeTables=None): - - self.metadataA, self.metadataB = metadataA, metadataB - self.labelA, self.labelB = labelA, labelB - self.label_width = max(len(labelA),len(labelB)) - excludeTables = set(excludeTables or []) - - A_table_names = set(metadataA.tables.keys()) - B_table_names = set(metadataB.tables.keys()) - - self.tables_missing_from_A = sorted( - B_table_names - A_table_names - excludeTables - ) - self.tables_missing_from_B = sorted( - A_table_names - B_table_names - excludeTables - ) - - self.tables_different = {} - for table_name in A_table_names.intersection(B_table_names): - - td = TableDiff() - - A_table = metadataA.tables[table_name] - B_table = metadataB.tables[table_name] - - A_column_names = set(A_table.columns.keys()) - B_column_names = set(B_table.columns.keys()) - - td.columns_missing_from_A = sorted( - B_column_names - A_column_names - ) - - td.columns_missing_from_B = sorted( - A_column_names - B_column_names - ) - - td.columns_different = {} - - for col_name in A_column_names.intersection(B_column_names): - - cd = ColDiff( - A_table.columns.get(col_name), - B_table.columns.get(col_name) - ) - - if cd: - td.columns_different[col_name]=cd - - # XXX - index and constraint differences should - # be checked for here - - if td: - self.tables_different[table_name]=td - - def __str__(self): - """ Summarize differences. """ - out = [] - column_template =' %%%is: %%r' % self.label_width - - for names,label in ( - (self.tables_missing_from_A,self.labelA), - (self.tables_missing_from_B,self.labelB), - ): - if names: - out.append( - ' tables missing from %s: %s' % ( - label,', '.join(sorted(names)) - ) - ) - - for name,td in sorted(self.tables_different.items()): - out.append( - ' table with differences: %s' % name - ) - for names,label in ( - (td.columns_missing_from_A,self.labelA), - (td.columns_missing_from_B,self.labelB), - ): - if names: - out.append( - ' %s missing these columns: %s' % ( - label,', '.join(sorted(names)) - ) - ) - for name,cd in td.columns_different.items(): - out.append(' column with differences: %s' % name) - out.append(column_template % (self.labelA,cd.col_A)) - out.append(column_template % (self.labelB,cd.col_B)) - - if out: - out.insert(0, 'Schema diffs:') - return '\n'.join(out) - else: - return 'No schema diffs' - - def __len__(self): - """ - Used in bool evaluation, return of 0 means no diffs. - """ - return ( - len(self.tables_missing_from_A) + - len(self.tables_missing_from_B) + - len(self.tables_different) - ) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/script/__init__.py --- a/kallithea/lib/dbmigrate/migrate/versioning/script/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -from kallithea.lib.dbmigrate.migrate.versioning.script.base import BaseScript -from kallithea.lib.dbmigrate.migrate.versioning.script.py import PythonScript -from kallithea.lib.dbmigrate.migrate.versioning.script.sql import SqlScript diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/script/base.py --- a/kallithea/lib/dbmigrate/migrate/versioning/script/base.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -import logging - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.versioning.config import operations -from kallithea.lib.dbmigrate.migrate.versioning import pathed - - -log = logging.getLogger(__name__) - -class BaseScript(pathed.Pathed): - """Base class for other types of scripts. - All scripts have the following properties: - - source (script.source()) - The source code of the script - version (script.version()) - The version number of the script - operations (script.operations()) - The operations defined by the script: upgrade(), downgrade() or both. - Returns a tuple of operations. - Can also check for an operation with ex. script.operation(Script.ops.up) - """ # TODO: sphinxfy this and implement it correctly - - def __init__(self, path): - log.debug('Loading script %s...', path) - self.verify(path) - super(BaseScript, self).__init__(path) - log.debug('Script %s loaded successfully', path) - - @classmethod - def verify(cls, path): - """Ensure this is a valid script - This version simply ensures the script file's existence - - :raises: :exc:`InvalidScriptError ` - """ - try: - cls.require_found(path) - except: - raise exceptions.InvalidScriptError(path) - - def source(self): - """:returns: source code of the script. - :rtype: string - """ - fd = open(self.path) - ret = fd.read() - fd.close() - return ret - - def run(self, engine): - """Core of each BaseScript subclass. - This method executes the script. - """ - raise NotImplementedError() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/script/py.py --- a/kallithea/lib/dbmigrate/migrate/versioning/script/py.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -import shutil -import warnings -import logging -import inspect -from StringIO import StringIO - -from kallithea.lib.dbmigrate import migrate -from kallithea.lib.dbmigrate.migrate.versioning import genmodel, schemadiff -from kallithea.lib.dbmigrate.migrate.versioning.config import operations -from kallithea.lib.dbmigrate.migrate.versioning.template import Template -from kallithea.lib.dbmigrate.migrate.versioning.script import base -from kallithea.lib.dbmigrate.migrate.versioning.util import import_path, load_model, with_engine -from kallithea.lib.dbmigrate.migrate.exceptions import MigrateDeprecationWarning, InvalidScriptError, ScriptError - -log = logging.getLogger(__name__) -__all__ = ['PythonScript'] - - -class PythonScript(base.BaseScript): - """Base for Python scripts""" - - @classmethod - def create(cls, path, **opts): - """Create an empty migration script at specified path - - :returns: :class:`PythonScript instance `""" - cls.require_notfound(path) - - src = Template(opts.pop('templates_path', None)).get_script(theme=opts.pop('templates_theme', None)) - shutil.copy(src, path) - - return cls(path) - - @classmethod - def make_update_script_for_model(cls, engine, oldmodel, - model, repository, **opts): - """Create a migration script based on difference between two SA models. - - :param repository: path to migrate repository - :param oldmodel: dotted.module.name:SAClass or SAClass object - :param model: dotted.module.name:SAClass or SAClass object - :param engine: SQLAlchemy engine - :type repository: string or :class:`Repository instance ` - :type oldmodel: string or Class - :type model: string or Class - :type engine: Engine instance - :returns: Upgrade / Downgrade script - :rtype: string - """ - - if isinstance(repository, basestring): - # oh dear, an import cycle! - from kallithea.lib.dbmigrate.migrate.versioning.repository import Repository - repository = Repository(repository) - - oldmodel = load_model(oldmodel) - model = load_model(model) - - # Compute differences. - diff = schemadiff.getDiffOfModelAgainstModel( - model, - oldmodel, - excludeTables=[repository.version_table]) - # TODO: diff can be False (there is no difference?) - decls, upgradeCommands, downgradeCommands = \ - genmodel.ModelGenerator(diff,engine).genB2AMigration() - - # Store differences into file. - src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None)) - f = open(src) - contents = f.read() - f.close() - - # generate source - search = 'def upgrade(migrate_engine):' - contents = contents.replace(search, '\n\n'.join((decls, search)), 1) - if upgradeCommands: - contents = contents.replace(' pass', upgradeCommands, 1) - if downgradeCommands: - contents = contents.replace(' pass', downgradeCommands, 1) - return contents - - @classmethod - def verify_module(cls, path): - """Ensure path is a valid script - - :param path: Script location - :type path: string - :raises: :exc:`InvalidScriptError ` - :returns: Python module - """ - # Try to import and get the upgrade() func - module = import_path(path) - try: - assert callable(module.upgrade) - except Exception as e: - raise InvalidScriptError(path + ': %s' % str(e)) - return module - - def preview_sql(self, url, step, **args): - """Mocks SQLAlchemy Engine to store all executed calls in a string - and runs :meth:`PythonScript.run ` - - :returns: SQL file - """ - buf = StringIO() - args['engine_arg_strategy'] = 'mock' - args['engine_arg_executor'] = lambda s, p = '': buf.write(str(s) + p) - - @with_engine - def go(url, step, **kw): - engine = kw.pop('engine') - self.run(engine, step) - return buf.getvalue() - - return go(url, step, **args) - - def run(self, engine, step): - """Core method of Script file. - Executes :func:`update` or :func:`downgrade` functions - - :param engine: SQLAlchemy Engine - :param step: Operation to run - :type engine: string - :type step: int - """ - if step > 0: - op = 'upgrade' - elif step < 0: - op = 'downgrade' - else: - raise ScriptError("%d is not a valid step" % step) - - funcname = base.operations[op] - script_func = self._func(funcname) - - # check for old way of using engine - if not inspect.getargspec(script_func)[0]: - raise TypeError("upgrade/downgrade functions must accept engine" - " parameter (since version 0.5.4)") - - script_func(engine) - - @property - def module(self): - """Calls :meth:`migrate.versioning.script.py.verify_module` - and returns it. - """ - if not hasattr(self, '_module'): - self._module = self.verify_module(self.path) - return self._module - - def _func(self, funcname): - if not hasattr(self.module, funcname): - msg = "Function '%s' is not defined in this script" - raise ScriptError(msg % funcname) - return getattr(self.module, funcname) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/script/sql.py --- a/kallithea/lib/dbmigrate/migrate/versioning/script/sql.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -import logging -import shutil - -from kallithea.lib.dbmigrate.migrate.versioning.script import base -from kallithea.lib.dbmigrate.migrate.versioning.template import Template - - -log = logging.getLogger(__name__) - -class SqlScript(base.BaseScript): - """A file containing plain SQL statements.""" - - @classmethod - def create(cls, path, **opts): - """Create an empty migration script at specified path - - :returns: :class:`SqlScript instance `""" - cls.require_notfound(path) - - src = Template(opts.pop('templates_path', None)).get_sql_script(theme=opts.pop('templates_theme', None)) - shutil.copy(src, path) - return cls(path) - - # TODO: why is step parameter even here? - def run(self, engine, step=None, executemany=True): - """Runs SQL script through raw dbapi execute call""" - text = self.source() - # Don't rely on SA's autocommit here - # (SA uses .startswith to check if a commit is needed. What if script - # starts with a comment?) - conn = engine.connect() - try: - trans = conn.begin() - try: - # HACK: SQLite doesn't allow multiple statements through - # its execute() method, but it provides executescript() instead - dbapi = conn.engine.raw_connection() - if executemany and getattr(dbapi, 'executescript', None): - dbapi.executescript(text) - else: - conn.execute(text) - trans.commit() - except: - trans.rollback() - raise - finally: - conn.close() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/shell.py --- a/kallithea/lib/dbmigrate/migrate/versioning/shell.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -"""The migrate command-line tool.""" - -import sys -import inspect -import logging -from optparse import OptionParser, BadOptionError - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.versioning import api -from kallithea.lib.dbmigrate.migrate.versioning.config import * -from kallithea.lib.dbmigrate.migrate.versioning.util import asbool - - -alias = dict( - s=api.script, - vc=api.version_control, - dbv=api.db_version, - v=api.version, -) - -def alias_setup(): - global alias - for key, val in alias.iteritems(): - setattr(api, key, val) -alias_setup() - - -class PassiveOptionParser(OptionParser): - - def _process_args(self, largs, rargs, values): - """little hack to support all --some_option=value parameters""" - - while rargs: - arg = rargs[0] - if arg == "--": - del rargs[0] - return - elif arg[0:2] == "--": - # if parser does not know about the option - # pass it along (make it anonymous) - try: - opt = arg.split('=', 1)[0] - self._match_long_opt(opt) - except BadOptionError: - largs.append(arg) - del rargs[0] - else: - self._process_long_opt(rargs, values) - elif arg[:1] == "-" and len(arg) > 1: - self._process_short_opts(rargs, values) - elif self.allow_interspersed_args: - largs.append(arg) - del rargs[0] - -def main(argv=None, **kwargs): - """Shell interface to :mod:`migrate.versioning.api`. - - kwargs are default options that can be overridden with passing - --some_option as command line option - - :param disable_logging: Let migrate configure logging - :type disable_logging: bool - """ - if argv is not None: - argv = argv - else: - argv = list(sys.argv[1:]) - commands = list(api.__all__) - commands.sort() - - usage = """%%prog COMMAND ... - - Available commands: - %s - - Enter "%%prog help COMMAND" for information on a particular command. - """ % '\n\t'.join(["%s - %s" % (command.ljust(28), api.command_desc.get(command)) for command in commands]) - - parser = PassiveOptionParser(usage=usage) - parser.add_option("-d", "--debug", - action="store_true", - dest="debug", - default=False, - help="Shortcut to turn on DEBUG mode for logging") - parser.add_option("-q", "--disable_logging", - action="store_true", - dest="disable_logging", - default=False, - help="Use this option to disable logging configuration") - help_commands = ['help', '-h', '--help'] - HELP = False - - try: - command = argv.pop(0) - if command in help_commands: - HELP = True - command = argv.pop(0) - except IndexError: - parser.print_help() - return - - command_func = getattr(api, command, None) - if command_func is None or command.startswith('_'): - parser.error("Invalid command %s" % command) - - parser.set_usage(inspect.getdoc(command_func)) - f_args, f_varargs, f_kwargs, f_defaults = inspect.getargspec(command_func) - for arg in f_args: - parser.add_option( - "--%s" % arg, - dest=arg, - action='store', - type="string") - - # display help of the current command - if HELP: - parser.print_help() - return - - options, args = parser.parse_args(argv) - - # override kwargs with anonymous parameters - override_kwargs = dict() - for arg in list(args): - if arg.startswith('--'): - args.remove(arg) - if '=' in arg: - opt, value = arg[2:].split('=', 1) - else: - opt = arg[2:] - value = True - override_kwargs[opt] = value - - # override kwargs with options if user is overwriting - for key, value in options.__dict__.iteritems(): - if value is not None: - override_kwargs[key] = value - - # arguments that function accepts without passed kwargs - f_required = list(f_args) - candidates = dict(kwargs) - candidates.update(override_kwargs) - for key, value in candidates.iteritems(): - if key in f_args: - f_required.remove(key) - - # map function arguments to parsed arguments - for arg in args: - try: - kw = f_required.pop(0) - except IndexError: - parser.error("Too many arguments for command %s: %s" % (command, - arg)) - kwargs[kw] = arg - - # apply overrides - kwargs.update(override_kwargs) - - # configure options - for key, value in options.__dict__.iteritems(): - kwargs.setdefault(key, value) - - # configure logging - if not asbool(kwargs.pop('disable_logging', False)): - # filter to log =< INFO into stdout and rest to stderr - class SingleLevelFilter(logging.Filter): - def __init__(self, min=None, max=None): - self.min = min or 0 - self.max = max or 100 - - def filter(self, record): - return self.min <= record.levelno <= self.max - - logger = logging.getLogger() - h1 = logging.StreamHandler(sys.stdout) - f1 = SingleLevelFilter(max=logging.INFO) - h1.addFilter(f1) - h2 = logging.StreamHandler(sys.stderr) - f2 = SingleLevelFilter(min=logging.WARN) - h2.addFilter(f2) - logger.addHandler(h1) - logger.addHandler(h2) - - if options.debug: - logger.setLevel(logging.DEBUG) - else: - logger.setLevel(logging.INFO) - - log = logging.getLogger(__name__) - - # check if all args are given - try: - num_defaults = len(f_defaults) - except TypeError: - num_defaults = 0 - f_args_default = f_args[len(f_args) - num_defaults:] - required = list(set(f_required) - set(f_args_default)) - if required: - parser.error("Not enough arguments for command %s: %s not specified" \ - % (command, ', '.join(required))) - - # handle command - try: - ret = command_func(**kwargs) - if ret is not None: - log.info(ret) - except (exceptions.UsageError, exceptions.KnownError) as e: - parser.error(e.args[0]) - -if __name__ == "__main__": - main() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/template.py --- a/kallithea/lib/dbmigrate/migrate/versioning/template.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -import os -import shutil -import sys - -from pkg_resources import resource_filename - -from kallithea.lib.dbmigrate.migrate.versioning.config import * -from kallithea.lib.dbmigrate.migrate.versioning import pathed - - -class Collection(pathed.Pathed): - """A collection of templates of a specific type""" - _mask = None - - def get_path(self, file): - return os.path.join(self.path, str(file)) - - -class RepositoryCollection(Collection): - _mask = '%s' - -class ScriptCollection(Collection): - _mask = '%s.py_tmpl' - -class ManageCollection(Collection): - _mask = '%s.py_tmpl' - -class SQLScriptCollection(Collection): - _mask = '%s.py_tmpl' - -class Template(pathed.Pathed): - """Finds the paths/packages of various Migrate templates. - - :param path: Templates are loaded from kallithea.lib.dbmigrate.migrate package - if `path` is not provided. - """ - pkg = 'kallithea.lib.dbmigrate.migrate.versioning.templates' - _manage = 'manage.py_tmpl' - - def __new__(cls, path=None): - if path is None: - path = cls._find_path(cls.pkg) - return super(Template, cls).__new__(cls, path) - - def __init__(self, path=None): - if path is None: - path = Template._find_path(self.pkg) - super(Template, self).__init__(path) - self.repository = RepositoryCollection(os.path.join(path, 'repository')) - self.script = ScriptCollection(os.path.join(path, 'script')) - self.manage = ManageCollection(os.path.join(path, 'manage')) - self.sql_script = SQLScriptCollection(os.path.join(path, 'sql_script')) - - @classmethod - def _find_path(cls, pkg): - """Returns absolute path to dotted python package.""" - tmp_pkg = pkg.rsplit('.', 1) - - if len(tmp_pkg) != 1: - return resource_filename(tmp_pkg[0], tmp_pkg[1]) - else: - return resource_filename(tmp_pkg[0], '') - - def _get_item(self, collection, theme=None): - """Locates and returns collection. - - :param collection: name of collection to locate - :param type_: type of subfolder in collection (defaults to "_default") - :returns: (package, source) - :rtype: str, str - """ - item = getattr(self, collection) - theme_mask = getattr(item, '_mask') - theme = theme_mask % (theme or 'default') - return item.get_path(theme) - - def get_repository(self, *a, **kw): - """Calls self._get_item('repository', *a, **kw)""" - return self._get_item('repository', *a, **kw) - - def get_script(self, *a, **kw): - """Calls self._get_item('script', *a, **kw)""" - return self._get_item('script', *a, **kw) - - def get_sql_script(self, *a, **kw): - """Calls self._get_item('sql_script', *a, **kw)""" - return self._get_item('sql_script', *a, **kw) - - def get_manage(self, *a, **kw): - """Calls self._get_item('manage', *a, **kw)""" - return self._get_item('manage', *a, **kw) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/__init__.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -#!/usr/bin/env python2 -from migrate.versioning.shell import main - -if __name__ == '__main__': - main(%(defaults)s) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -#!/usr/bin/env python2 -from migrate.versioning.shell import main - -{{py: -_vars = locals().copy() -del _vars['__template_name__'] -_vars.pop('repository_name', None) -defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) -}} - -if __name__ == '__main__': - main({{ defaults }}) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -import sys - -from sqlalchemy import engine_from_config -from paste.deploy.loadwsgi import ConfigLoader - -from migrate.versioning.shell import main -from {{ locals().pop('repository_name') }}.model import migrations - - -if '-c' in sys.argv: - pos = sys.argv.index('-c') - conf_path = sys.argv[pos + 1] - del sys.argv[pos:pos + 2] -else: - conf_path = 'development.ini' - -{{py: -_vars = locals().copy() -del _vars['__template_name__'] -defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) -}} - -conf_dict = ConfigLoader(conf_path).parser._sections['app:main'] - -# migrate supports passing url as an existing Engine instance (since 0.6.0) -# usage: migrate -c path/to/config.ini COMMANDS -if __name__ == '__main__': - main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }}) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/__init__.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/README --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/README Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -This is a database migration repository. - -More information at -http://code.google.com/p/sqlalchemy-migrate/ diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/__init__.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -[db_settings] -# Used to identify which repository this database is versioned under. -# You can use the name of your project. -repository_id={{ locals().pop('repository_id') }} - -# The name of the database table used to track the schema version. -# This name shouldn't already be used by your project. -# If this is changed once a database is under version control, you'll need to -# change the table name in each database too. -version_table={{ locals().pop('version_table') }} - -# When committing a change script, Migrate will attempt to generate the -# sql for all supported databases; normally, if one of them fails - probably -# because you don't have that database installed - it is ignored and the -# commit continues, perhaps ending successfully. -# Databases in this list MUST compile successfully during a commit, or the -# entire commit will fail. List the databases your application will actually -# be using to ensure your updates to that database work properly. -# This must be a list; example: ['postgres','sqlite'] -required_dbs={{ locals().pop('required_dbs') }} - -# When creating new change scripts, Migrate will stamp the new script with -# a version number. By default this is latest_version + 1. You can set this -# to 'true' to tell Migrate to use the UTC timestamp instead. -use_timestamp_numbering='false' diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/versions/__init__.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -This is a database migration repository. - -More information at -http://code.google.com/p/sqlalchemy-migrate/ diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/__init__.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -[db_settings] -# Used to identify which repository this database is versioned under. -# You can use the name of your project. -repository_id={{ locals().pop('repository_id') }} - -# The name of the database table used to track the schema version. -# This name shouldn't already be used by your project. -# If this is changed once a database is under version control, you'll need to -# change the table name in each database too. -version_table={{ locals().pop('version_table') }} - -# When committing a change script, Migrate will attempt to generate the -# sql for all supported databases; normally, if one of them fails - probably -# because you don't have that database installed - it is ignored and the -# commit continues, perhaps ending successfully. -# Databases in this list MUST compile successfully during a commit, or the -# entire commit will fail. List the databases your application will actually -# be using to ensure your updates to that database work properly. -# This must be a list; example: ['postgres','sqlite'] -required_dbs={{ locals().pop('required_dbs') }} diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/versions/__init__.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/script/__init__.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -from sqlalchemy import * -from migrate import * - -def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; bind migrate_engine - # to your metadata - pass - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - pass diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl --- a/kallithea/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -from sqlalchemy import * -from migrate import * - -def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; bind migrate_engine - # to your metadata - pass - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - pass diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/sql_script/default.py_tmpl diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/templates/sql_script/pylons.py_tmpl diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/util/__init__.py --- a/kallithea/lib/dbmigrate/migrate/versioning/util/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,179 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -""".. currentmodule:: migrate.versioning.util""" - -import warnings -import logging -from decorator import decorator -from pkg_resources import EntryPoint - -from sqlalchemy import create_engine -from sqlalchemy.engine import Engine -from sqlalchemy.pool import StaticPool - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.versioning.util.keyedinstance import KeyedInstance -from kallithea.lib.dbmigrate.migrate.versioning.util.importpath import import_path - - -log = logging.getLogger(__name__) - -def load_model(dotted_name): - """Import module and use module-level variable". - - :param dotted_name: path to model in form of string: ``some.python.module:Class`` - - .. versionchanged:: 0.5.4 - - """ - if isinstance(dotted_name, basestring): - if ':' not in dotted_name: - # backwards compatibility - warnings.warn('model should be in form of module.model:User ' - 'and not module.model.User', exceptions.MigrateDeprecationWarning) - dotted_name = ':'.join(dotted_name.rsplit('.', 1)) - return EntryPoint.parse('x=%s' % dotted_name).load(False) - else: - # Assume it's already loaded. - return dotted_name - -def asbool(obj): - """Do everything to use object as bool""" - if isinstance(obj, basestring): - obj = obj.strip().lower() - if obj in ['true', 'yes', 'on', 'y', 't', '1']: - return True - elif obj in ['false', 'no', 'off', 'n', 'f', '0']: - return False - else: - raise ValueError("String is not true/false: %r" % obj) - if obj in (True, False): - return bool(obj) - else: - raise ValueError("String is not true/false: %r" % obj) - -def guess_obj_type(obj): - """Do everything to guess object type from string - - Tries to convert to `int`, `bool` and finally returns if not succeded. - - .. versionadded: 0.5.4 - """ - - result = None - - try: - result = int(obj) - except: - pass - - if result is None: - try: - result = asbool(obj) - except: - pass - - if result is not None: - return result - else: - return obj - -@decorator -def catch_known_errors(f, *a, **kw): - """Decorator that catches known api errors - - .. versionadded: 0.5.4 - """ - - try: - return f(*a, **kw) - except exceptions.PathFoundError as e: - raise exceptions.KnownError("The path %s already exists" % e.args[0]) - -def construct_engine(engine, **opts): - """.. versionadded:: 0.5.4 - - Constructs and returns SQLAlchemy engine. - - Currently, there are 2 ways to pass create_engine options to :mod:`migrate.versioning.api` functions: - - :param engine: connection string or a existing engine - :param engine_dict: python dictionary of options to pass to `create_engine` - :param engine_arg_*: keyword parameters to pass to `create_engine` (evaluated with :func:`migrate.versioning.util.guess_obj_type`) - :type engine_dict: dict - :type engine: string or Engine instance - :type engine_arg_*: string - :returns: SQLAlchemy Engine - - .. note:: - - keyword parameters override ``engine_dict`` values. - - """ - if isinstance(engine, Engine): - return engine - elif not isinstance(engine, basestring): - raise ValueError("you need to pass either an existing engine or a database uri") - - # get options for create_engine - if opts.get('engine_dict') and isinstance(opts['engine_dict'], dict): - kwargs = opts['engine_dict'] - else: - kwargs = dict() - - # DEPRECATED: handle echo the old way - echo = asbool(opts.get('echo', False)) - if echo: - warnings.warn('echo=True parameter is deprecated, pass ' - 'engine_arg_echo=True or engine_dict={"echo": True}', - exceptions.MigrateDeprecationWarning) - kwargs['echo'] = echo - - # parse keyword arguments - for key, value in opts.iteritems(): - if key.startswith('engine_arg_'): - kwargs[key[11:]] = guess_obj_type(value) - - log.debug('Constructing engine') - # TODO: return create_engine(engine, poolclass=StaticPool, **kwargs) - # seems like 0.5.x branch does not work with engine.dispose and staticpool - return create_engine(engine, **kwargs) - -@decorator -def with_engine(f, *a, **kw): - """Decorator for :mod:`migrate.versioning.api` functions - to safely close resources after function usage. - - Passes engine parameters to :func:`construct_engine` and - resulting parameter is available as kw['engine']. - - Engine is disposed after wrapped function is executed. - - .. versionadded: 0.6.0 - """ - url = a[0] - engine = construct_engine(url, **kw) - - try: - kw['engine'] = engine - return f(*a, **kw) - finally: - if isinstance(engine, Engine) and engine is not url: - log.debug('Disposing SQLAlchemy engine %s', engine) - engine.dispose() - - -class Memoize: - """Memoize(fn) - an instance which acts like fn but memoizes its arguments - Will only work on functions with non-mutable arguments - - ActiveState Code 52201 - """ - def __init__(self, fn): - self.fn = fn - self.memo = {} - - def __call__(self, *args): - if not self.memo.has_key(args): - self.memo[args] = self.fn(*args) - return self.memo[args] diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/util/importpath.py --- a/kallithea/lib/dbmigrate/migrate/versioning/util/importpath.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -import os -import sys - -def import_path(fullpath): - """ Import a file with full path specification. Allows one to - import from anywhere, something __import__ does not do. - """ - # http://zephyrfalcon.org/weblog/arch_d7_2002_08_31.html - path, filename = os.path.split(fullpath) - filename, ext = os.path.splitext(filename) - sys.path.append(path) - module = __import__(filename) - reload(module) # Might be out of date during tests - del sys.path[-1] - return module diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/util/keyedinstance.py --- a/kallithea/lib/dbmigrate/migrate/versioning/util/keyedinstance.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -class KeyedInstance(object): - """A class whose instances have a unique identifier of some sort - No two instances with the same unique ID should exist - if we try to create - a second instance, the first should be returned. - """ - - _instances = dict() - - def __new__(cls, *p, **k): - instances = cls._instances - clskey = str(cls) - if clskey not in instances: - instances[clskey] = dict() - instances = instances[clskey] - - key = cls._key(*p, **k) - if key not in instances: - instances[key] = super(KeyedInstance, cls).__new__(cls) - return instances[key] - - @classmethod - def _key(cls, *p, **k): - """Given a unique identifier, return a dictionary key - This should be overridden by child classes, to specify which parameters - should determine an object's uniqueness - """ - raise NotImplementedError() - - @classmethod - def clear(cls): - # Allow cls.clear() as well as uniqueInstance.clear(cls) - if str(cls) in cls._instances: - del cls._instances[str(cls)] diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/migrate/versioning/version.py --- a/kallithea/lib/dbmigrate/migrate/versioning/version.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,238 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -import os -import re -import shutil -import logging - -from kallithea.lib.dbmigrate.migrate import exceptions -from kallithea.lib.dbmigrate.migrate.versioning import pathed, script -from datetime import datetime - - -log = logging.getLogger(__name__) - -class VerNum(object): - """A version number that behaves like a string and int at the same time""" - - _instances = dict() - - def __new__(cls, value): - val = str(value) - if val not in cls._instances: - cls._instances[val] = super(VerNum, cls).__new__(cls) - ret = cls._instances[val] - return ret - - def __init__(self,value): - self.value = str(int(value)) - if self < 0: - raise ValueError("Version number cannot be negative") - - def __add__(self, value): - ret = int(self) + int(value) - return VerNum(ret) - - def __sub__(self, value): - return self + (int(value) * -1) - - def __cmp__(self, value): - return int(self) - int(value) - - def __repr__(self): - return "" % self.value - - def __str__(self): - return str(self.value) - - def __int__(self): - return int(self.value) - - -class Collection(pathed.Pathed): - """A collection of versioning scripts in a repository""" - - FILENAME_WITH_VERSION = re.compile(r'^(\d{3,}).*') - - def __init__(self, path): - """Collect current version scripts in repository - and store them in self.versions - """ - super(Collection, self).__init__(path) - - # Create temporary list of files, allowing skipped version numbers. - files = os.listdir(path) - if '1' in files: - # deprecation - raise Exception('It looks like you have a repository in the old ' - 'format (with directories for each version). ' - 'Please convert repository before proceeding.') - - tempVersions = dict() - for filename in files: - match = self.FILENAME_WITH_VERSION.match(filename) - if match: - num = int(match.group(1)) - tempVersions.setdefault(num, []).append(filename) - else: - pass # Must be a helper file or something, let's ignore it. - - # Create the versions member where the keys - # are VerNum's and the values are Version's. - self.versions = dict() - for num, files in tempVersions.items(): - self.versions[VerNum(num)] = Version(num, path, files) - - @property - def latest(self): - """:returns: Latest version in Collection""" - return max([VerNum(0)] + self.versions.keys()) - - def _next_ver_num(self, use_timestamp_numbering): - if use_timestamp_numbering: - return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S'))) - else: - return self.latest + 1 - - def create_new_python_version(self, description, **k): - """Create Python files for new version""" - ver = self._next_ver_num(k.pop('use_timestamp_numbering', False)) - extra = str_to_filename(description) - - if extra: - if extra == '_': - extra = '' - elif not extra.startswith('_'): - extra = '_%s' % extra - - filename = '%03d%s.py' % (ver, extra) - filepath = self._version_path(filename) - - script.PythonScript.create(filepath, **k) - self.versions[ver] = Version(ver, self.path, [filename]) - - def create_new_sql_version(self, database, description, **k): - """Create SQL files for new version""" - ver = self._next_ver_num(k.pop('use_timestamp_numbering', False)) - self.versions[ver] = Version(ver, self.path, []) - - extra = str_to_filename(description) - - if extra: - if extra == '_': - extra = '' - elif not extra.startswith('_'): - extra = '_%s' % extra - - # Create new files. - for op in ('upgrade', 'downgrade'): - filename = '%03d%s_%s_%s.sql' % (ver, extra, database, op) - filepath = self._version_path(filename) - script.SqlScript.create(filepath, **k) - self.versions[ver].add_script(filepath) - - def version(self, vernum=None): - """Returns latest Version if vernum is not given. - Otherwise, returns wanted version""" - if vernum is None: - vernum = self.latest - return self.versions[VerNum(vernum)] - - @classmethod - def clear(cls): - super(Collection, cls).clear() - - def _version_path(self, ver): - """Returns path of file in versions repository""" - return os.path.join(self.path, str(ver)) - - -class Version(object): - """A single version in a collection - :param vernum: Version Number - :param path: Path to script files - :param filelist: List of scripts - :type vernum: int, VerNum - :type path: string - :type filelist: list - """ - - def __init__(self, vernum, path, filelist): - self.version = VerNum(vernum) - - # Collect scripts in this folder - self.sql = dict() - self.python = None - - for script in filelist: - self.add_script(os.path.join(path, script)) - - def script(self, database=None, operation=None): - """Returns SQL or Python Script""" - for db in (database, 'default'): - # Try to return a .sql script first - try: - return self.sql[db][operation] - except KeyError: - continue # No .sql script exists - - # TODO: maybe add force Python parameter? - ret = self.python - - assert ret is not None, \ - "There is no script for %d version" % self.version - return ret - - def add_script(self, path): - """Add script to Collection/Version""" - if path.endswith(Extensions.py): - self._add_script_py(path) - elif path.endswith(Extensions.sql): - self._add_script_sql(path) - - SQL_FILENAME = re.compile(r'^.*\.sql') - - def _add_script_sql(self, path): - basename = os.path.basename(path) - match = self.SQL_FILENAME.match(basename) - - if match: - basename = basename.replace('.sql', '') - parts = basename.split('_') - if len(parts) < 3: - raise exceptions.ScriptError( - "Invalid SQL script name %s " % basename + \ - "(needs to be ###_description_database_operation.sql)") - version = parts[0] - op = parts[-1] - dbms = parts[-2] - else: - raise exceptions.ScriptError( - "Invalid SQL script name %s " % basename + \ - "(needs to be ###_description_database_operation.sql)") - - # File the script into a dictionary - self.sql.setdefault(dbms, {})[op] = script.SqlScript(path) - - def _add_script_py(self, path): - if self.python is not None: - raise exceptions.ScriptError('You can only have one Python script ' - 'per version, but you have: %s and %s' % (self.python, path)) - self.python = script.PythonScript(path) - - -class Extensions: - """A namespace for file extensions""" - py = 'py' - sql = 'sql' - -def str_to_filename(s): - """Replaces spaces, (double and single) quotes - and double underscores to underscores - """ - - s = s.replace(' ', '_').replace('"', '_').replace("'", '_').replace(".", "_") - while '__' in s: - s = s.replace('__', '_') - return s diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/__init__.py --- a/kallithea/lib/dbmigrate/schema/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Schemas for migrations - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Nov 1, 2011 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_1_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_1_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -from sqlalchemy import * -from sqlalchemy.orm import relation, class_mapper -from sqlalchemy.orm.session import Session -from kallithea.model.meta import Base - -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) - return d - - def get_appstruct(self): - """return list with keys and values tuples 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) - Session.commit() - - -class UserFollowing(Base, BaseModel): - __tablename__ = 'user_followings' - __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'), - UniqueConstraint('user_id', 'follows_user_id') - , {'useexisting':True}) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None) - - user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relation('Repository') - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True}) - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_args = Column("cache_args", String(length=None, 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 __repr__(self): - return "" % (self.cache_id, self.cache_key) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_2_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_2_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1097 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_2_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea <=1.2.X - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import logging -import datetime -import traceback -from datetime import date - -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 kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - generate_api_key, safe_unicode -from kallithea.lib.exceptions import UserGroupsAssignedException -from kallithea.lib.compat import json - -from kallithea.model.meta import Base, Session -from kallithea.lib.caching_query import FromCache - -from kallithea import DB_PREFIX - -log = logging.getLogger(__name__) - -#============================================================================== -# BASE CLASSES -#============================================================================== - -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 classes - - """ - - @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) - return d - - def get_appstruct(self): - """return list with keys and values tuples 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) - Session.commit() - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True}) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, k='', v=''): - self.app_settings_name = k - self.app_settings_value = v - - - @validates('_app_settings_value') - def validate_settings_value(self, key, val): - assert type(val) == unicode - return val - - @hybrid_property - def app_settings_value(self): - v = self._app_settings_value - if v == 'ldap_active': - v = str2bool(v) - return v - - @app_settings_value.setter - def app_settings_value(self, val): - """ - Setter that will always make sure we use unicode in app_settings_value - - :param val: - """ - self._app_settings_value = safe_unicode(val) - - def __repr__(self): - return "<%s('%s:%s')>" % (self.__class__.__name__, - self.app_settings_name, self.app_settings_value) - - - @classmethod - def get_by_name(cls, ldap_key): - return cls.query()\ - .filter(cls.app_settings_name == ldap_key).scalar() - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name:row.app_settings_value}) - - return fd - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True}) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'pretxnchangegroup.push_logger' - HOOK_PULL = 'preoutgoing.pull_logger' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key) - - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, - cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, - cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key).scalar() or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session.add(new_ui) - Session.commit() - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True}) - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=None) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - user_log = relationship('UserLog', cascade='all') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - @property - def full_contact(self): - return '%s %s <%s>' % (self.name, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.name, self.lastname) - - @property - def is_admin(self): - return self.admin - - def __repr__(self): - try: - return "<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - except: - return self.__class__.__name__ - - @classmethod - def get_by_username(cls, username, case_insensitive=False): - if case_insensitive: - return Session.query(cls).filter(cls.username.ilike(username)).scalar() - else: - return Session.query(cls).filter(cls.username == username).scalar() - - @classmethod - def get_by_api_key(cls, api_key): - return cls.query().filter(cls.api_key == api_key).one() - - def update_lastlogin(self): - """Update user lastlogin""" - - self.last_login = datetime.datetime.now() - Session.add(self) - Session.commit() - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def create(cls, form_data): - from kallithea.lib.auth import get_crypt_password - - try: - new_user = cls() - for k, v in form_data.items(): - if k == 'password': - v = get_crypt_password(v) - setattr(new_user, k, v) - - new_user.api_key = generate_api_key() - Session.add(new_user) - Session.commit() - return new_user - except: - log.error(traceback.format_exc()) - Session.rollback() - raise - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = {'extend_existing':True} - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - @property - def action_as_day(self): - return date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = {'extend_existing':True} - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - - def __repr__(self): - return '' % (self.users_group_name) - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.users_group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.users_group_name == group_name) - if cache: - gr = gr.options(FromCache("sql_cache_short", - "get_user_%s" % group_name)) - return gr.scalar() - - - @classmethod - def get(cls, users_group_id, cache=False): - users_group = cls.query() - if cache: - users_group = users_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % users_group_id)) - return users_group.get(users_group_id) - - @classmethod - def create(cls, form_data): - try: - new_users_group = cls() - for k, v in form_data.items(): - setattr(new_users_group, k, v) - - Session.add(new_users_group) - Session.commit() - return new_users_group - except: - log.error(traceback.format_exc()) - Session.rollback() - raise - - @classmethod - def update(cls, users_group_id, form_data): - - try: - users_group = cls.get(users_group_id, cache=False) - - for k, v in form_data.items(): - if k == 'users_group_members': - users_group.members = [] - Session.flush() - members_list = [] - if v: - v = [v] if isinstance(v, basestring) else v - for u_id in set(v): - member = UserGroupMember(users_group_id, u_id) - members_list.append(member) - setattr(users_group, 'members', members_list) - setattr(users_group, k, v) - - Session.add(users_group) - Session.commit() - except: - log.error(traceback.format_exc()) - Session.rollback() - raise - - @classmethod - def delete(cls, users_group_id): - try: - - # check if this group is not assigned to repo - assigned_groups = UserGroupRepoToPerm.query()\ - .filter(UserGroupRepoToPerm.users_group_id == - users_group_id).all() - - if assigned_groups: - raise UserGroupsAssignedException('RepoGroup assigned to %s' % - assigned_groups) - - users_group = cls.get(users_group_id, cache=False) - Session.delete(users_group) - Session.commit() - except: - log.error(traceback.format_exc()) - Session.rollback() - raise - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = {'extend_existing':True} - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - @staticmethod - def add_user_to_group(group, user): - ugm = UserGroupMember() - ugm.users_group = group - ugm.user = user - Session.add(ugm) - Session.commit() - return ugm - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg') - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all') - - logs = relationship('UserLog', cascade='all') - - def __repr__(self): - return "<%s('%s:%s')>" % (self.__class__.__name__, - self.repo_id, self.repo_name) - - @classmethod - def url_sep(cls): - return '/' - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session.query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .options(joinedload(Repository.user))\ - .options(joinedload(Repository.group)) - return q.one() - - @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(Ui).filter(Ui.ui_key == - cls.url_sep()) - 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(Ui).filter(Ui.ui_key == - Repository.url_sep()) - 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 = Ui.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 kallithea.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 - - #========================================================================== - # 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 - - 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) - else: - repo = backend(repo_full_path, create=False) - - return repo - - -class Group(Base, BaseModel): - __tablename__ = 'groups' - __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'), - CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},) - __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) - - parent_group = relationship('Group', remote_side=group_id) - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __repr__(self): - return "<%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" % 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 infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return Group.query().filter(Group.parent_group == self) - - @property - def name(self): - return self.group_name.split(Group.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(Group.url_sep()) - - @property - def repositories(self): - return Repository.query().filter(Repository.group == self) - - @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 Group.url_sep().join(path_prefix + [group_name]) - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = {'extend_existing':True} - 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 __repr__(self): - return "<%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() - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True}) - 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') - permission = relationship('Permission') - repository = relationship('Repository') - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True}) - 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') - - @classmethod - def has_perm(cls, user_id, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - - return cls.query().filter(cls.user_id == user_id)\ - .filter(cls.permission == perm).scalar() is not None - - @classmethod - def grant_perm(cls, user_id, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - - new = cls() - new.user_id = user_id - new.permission = perm - try: - Session.add(new) - Session.commit() - except: - Session.rollback() - - - @classmethod - def revoke_perm(cls, user_id, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - - try: - cls.query().filter(cls.user_id == user_id) \ - .filter(cls.permission == perm).delete() - Session.commit() - except: - Session.rollback() - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True}) - 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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - def __repr__(self): - return ' %s >' % (self.users_group, self.repository) - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = {'extend_existing':True} - 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('UserGroup') - permission = relationship('Permission') - - - @classmethod - def has_perm(cls, users_group_id, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - - return cls.query().filter(cls.users_group_id == - users_group_id)\ - .filter(cls.permission == perm)\ - .scalar() is not None - - @classmethod - def grant_perm(cls, users_group_id, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - - new = cls() - new.users_group_id = users_group_id - new.permission = perm - try: - Session.add(new) - Session.commit() - except: - Session.rollback() - - - @classmethod - def revoke_perm(cls, users_group_id, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - - try: - cls.query().filter(cls.users_group_id == users_group_id) \ - .filter(cls.permission == perm).delete() - Session.commit() - except: - Session.rollback() - - -class UserRepoGroupToPerm(Base, BaseModel): - __tablename__ = 'group_to_perm' - __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True}) - - 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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - permission = relationship('Permission') - group = relationship('RepoGroup') - -class Statistics(Base, BaseModel): - __tablename__ = 'statistics' - __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True}) - 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}) - - 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}) - 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 __repr__(self): - return "<%s('%s:%s')>" % (self.__class__.__name__, - self.cache_id, self.cache_key) - - @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: - """ - return cls.query()\ - .filter(CacheInvalidation.cache_key == key)\ - .filter(CacheInvalidation.cache_active == False)\ - .scalar() - - @classmethod - def set_invalidate(cls, key): - """ - Mark this Cache key for invalidation - - :param key: - """ - - log.debug('marking %s for invalidation', key) - inv_obj = Session.query(cls)\ - .filter(cls.cache_key == key).scalar() - if inv_obj: - inv_obj.cache_active = False - else: - log.debug('cache key not found in invalidation db -> creating one') - inv_obj = CacheInvalidation(key) - - try: - 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 = Session.query(CacheInvalidation)\ - .filter(CacheInvalidation.cache_key == key).scalar() - inv_obj.cache_active = True - Session.add(inv_obj) - Session.commit() - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = {'extend_existing':True} - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_3_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_3_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1322 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_3_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea <=1.3.X - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. - -""" - - -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 kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session -import hashlib - -from kallithea import DB_PREFIX - -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 tuples 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 Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + '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[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name:row.app_settings_value}) - - return fd - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, '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('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - - @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 UserGroup(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('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', 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 UserGroupMember(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('UserGroup') - - 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('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all') - - logs = relationship('UserLog') - - 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(Ui)\ - .filter(Ui.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(Ui).filter(Ui.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 = Ui.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 kallithea.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 - - :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) - 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('UserGroupRepoGroupToPerm', 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 infinite 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 UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session.add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupToPerm(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('UserGroup') - 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 UserGroupRepoGroupToPerm(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('UserGroup') - 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 kallithea - prefix = '' - iid = kallithea.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 kallithea.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) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_4_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_4_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1814 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_4_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea <=1.4.X - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - - -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 beaker.cache import cache_region, region_invalidate -from webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_suffix -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -from kallithea import DB_PREFIX - -URL_SEP = '/' -log = logging.getLogger(__name__) - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - return cls.query().all() - - @classmethod - def delete(cls, id_): - obj = cls.query().get(id_) - Session().delete(obj) - - def __repr__(self): - if hasattr(self, '__unicode__'): - # python repr needs to return str - return safe_str(self.__unicode__()) - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, k='', v=''): - self.app_settings_name = k - self.app_settings_value = v - - @validates('_app_settings_value') - def validate_settings_value(self, key, val): - assert type(val) == unicode - return val - - @hybrid_property - def app_settings_value(self): - v = self._app_settings_value - if self.app_settings_name == '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, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key): - res = cls.get_by_name(key) - if not res: - res = cls(key) - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - DEFAULT_USER = 'default' - DEFAULT_PERMISSIONS = [ - 'hg.register.manual_activate', 'hg.create.repository', - 'hg.fork.repository', 'repository.read', 'group.read' - ] - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - user_log = relationship('UserLog', 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('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - def get_api_data(self): - """ - Common function for generating user related data for API - """ - user = self - data = dict( - user_id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - emails=user.emails, - api_key=user.api_key, - active=user.active, - admin=user.admin, - ldap_dn=user.ldap_dn, - last_login=user.last_login, - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - __mapper_args__ = {} - - email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - user = relationship('User', lazy='joined') - - @validates('_email') - def validate_email(self, key, email): - # check if this email is not main one - main_email = Session().query(User).filter(User.email == email).scalar() - if main_email is not None: - raise AttributeError('email %s is present is user table' % email) - return email - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', 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) - - 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 UserGroupMember(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('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - - logs = relationship('UserLog') - comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") - - pull_requests_org = relationship('PullRequest', - primaryjoin='PullRequest.org_repo_id==Repository.repo_id', - cascade="all, delete, delete-orphan") - - pull_requests_other = relationship('PullRequest', - primaryjoin='PullRequest.other_repo_id==Repository.repo_id', - cascade="all, delete, delete-orphan") - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, - self.repo_name) - - @hybrid_property - def locked(self): - # always should return [user_id, timelocked] - if self._locked: - _lock_info = self._locked.split(':') - return int(_lock_info[0]), _lock_info[1] - return [None, None] - - @locked.setter - def locked(self, val): - if val and isinstance(val, (list, tuple)): - self._locked = ':'.join(map(str, val)) - else: - self._locked = None - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .options(joinedload(Repository.user))\ - .options(joinedload(Repository.group)) - return q.scalar() - - @classmethod - def get_by_full_path(cls, repo_full_path): - repo_name = repo_full_path.split(cls.base_path(), 1)[-1] - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.ui_key == - Repository.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def repo_full_path(self): - p = [self.repo_path] - # we need to split the name by / since this is how we store the - # names in the database, but that eventually needs to be converted - # into a valid system path - p += self.repo_name.split(Repository.url_sep()) - return os.path.join(*p) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def inject_ui(cls, repo, extras={}): - from kallithea.lib.vcs.backends.hg import MercurialRepository - from kallithea.lib.vcs.backends.git import GitRepository - required = (MercurialRepository, GitRepository) - if not isinstance(repo, required): - raise Exception('repo must be instance of %s' % (','.join(required))) - - # inject ui extra param to log this action via push logger - for k, v in extras.items(): - repo._repo.ui.setconfig('extras', k, v) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None - ) - - return data - - @classmethod - def lock(cls, repo, user_id): - repo.locked = [user_id, time.time()] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @property - def last_db_change(self): - return self.updated_on - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_last_change(self, last_change=None): - if last_change is None: - last_change = datetime.datetime.now() - if self.updated_on is None or self.updated_on != last_change: - log.debug('updated repo %s with new date %s', self, last_change) - self.updated_on = last_change - Session().add(self) - Session().commit() - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - :type revisions: list - """ - - statuses = ChangesetStatus.query()\ - .filter(ChangesetStatus.repo == self)\ - .filter(ChangesetStatus.version == 0) - if revisions: - statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) - grouped = {} - - #maybe we have open new pullrequest without a status ? - stat = ChangesetStatus.STATUS_UNDER_REVIEW - status_lbl = ChangesetStatus.get_status_lbl(stat) - for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): - for rev in pr.revisions: - pr_id = pr.pull_request_id - pr_repo = pr.other_repo.repo_name - grouped[rev] = [stat, status_lbl, pr_id, pr_repo] - - for stat in statuses.all(): - pr_id = pr_repo = None - if stat.pull_request: - pr_id = stat.pull_request.pull_request_id - pr_repo = stat.pull_request.other_repo.repo_name - grouped[stat.revision] = [str(stat.status), stat.status_lbl, - pr_id, pr_repo] - return grouped - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - @property - def invalidate(self): - return CacheInvalidation.invalidate(self.repo_name) - - def set_invalidate(self): - """ - set a cache for invalidation for this instance - """ - CacheInvalidation.set_invalidate(repo_name=self.repo_name) - - @LazyProperty - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, cache_map=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - log.debug('Getting cached instance of repo') - - if cache_map: - # get using prefilled cache_map - invalidate_repo = cache_map[self.repo_name] - if invalidate_repo: - invalidate_repo = (None if invalidate_repo.cache_active - else invalidate_repo) - else: - # get from invalidate - invalidate_repo = self.invalidate - - if invalidate_repo is not None: - region_invalidate(_c, None, rn) - # update our cache - CacheInvalidation.set_valid(invalidate_repo.cache_key) - return _c(rn) - - def __get_instance(self): - repo_full_path = self.repo_full_path - try: - alias = get_scm(repo_full_path)[0] - log.debug('Creating instance of %s repository', alias) - backend = get_backend(alias) - except VCSError: - log.error(traceback.format_exc()) - log.error('Perhaps this repository is in db and not in ' - 'filesystem run rescan repositories with ' - '"destroy old data " option from admin panel') - return - - if alias == 'hg': - - repo = backend(safe_str(repo_full_path), create=False, - baseui=self._ui) - else: - repo = backend(repo_full_path, create=False) - - return repo - - -class RepoGroup(Base, BaseModel): - __tablename__ = 'groups' - __table_args__ = ( - UniqueConstraint('group_name', 'group_parent_id'), - CheckConstraint('group_id != group_parent_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - - parent_group = relationship('RepoGroup', remote_side=group_id) - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, check_perms=False): - from webhelpers.html import literal as _literal - from kallithea.model.scm import ScmModel - groups = cls.query().all() - if check_perms: - #filter group user have access to, it's done - #magically inside ScmModel based on current user - groups = ScmModel().get_repos_groups(groups) - repo_groups = [('', '')] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - all_ = [] - - def _get_members(root_gr): - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - PERMS = [ - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository Group no access')), - ('group.read', _('Repository Group read access')), - ('group.write', _('Repository Group write access')), - ('group.admin', _('Repository Group admin access')), - - ('hg.admin', _('Kallithea 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 Kallithea ' - 'with manual activation')), - - ('hg.register.auto_activate', _('Register new user with Kallithea ' - 'with auto activation')), - ] - - # defines which permissions are more important higher the more important - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository':1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - repository = relationship('Repository') - permission = relationship('Permission') - - @classmethod - def create(cls, user, repository, permission): - n = cls() - n.user = user - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %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 UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupToPerm(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('UserGroup') - 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 UserGroupRepoGroupToPerm(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('UserGroup') - permission = relationship('Permission') - group = relationship('RepoGroup') - - -class Statistics(Base, BaseModel): - __tablename__ = 'statistics' - __table_args__ = ( - UniqueConstraint('repository_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) - stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) - commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data - commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data - languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data - - repository = relationship('Repository', single_parent=True) - - -class UserFollowing(Base, BaseModel): - __tablename__ = 'user_followings' - __table_args__ = ( - UniqueConstraint('user_id', 'follows_repository_id'), - UniqueConstraint('user_id', 'follows_user_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, cache_args=''): - self.cache_key = cache_key - self.cache_args = cache_args - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, - self.cache_id, self.cache_key) - - @property - def prefix(self): - _split = self.cache_key.split(self.cache_args, 1) - if _split and len(_split) == 2: - return _split[0] - return '' - - @classmethod - def clear_cache(cls): - cls.query().delete() - - @classmethod - def _get_key(cls, key): - """ - Wrapper for generating a key, together with a prefix - - :param key: - """ - import kallithea - prefix = '' - org_key = key - iid = kallithea.CONFIG.get('instance_id') - if iid: - prefix = iid - - return "%s%s" % (prefix, key), prefix, org_key - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.cache_key == key).scalar() - - @classmethod - def get_by_repo_name(cls, repo_name): - return cls.query().filter(cls.cache_args == repo_name).all() - - @classmethod - def _get_or_create_key(cls, key, repo_name, commit=True): - inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() - if not inv_obj: - try: - inv_obj = CacheInvalidation(key, repo_name) - Session().add(inv_obj) - if commit: - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return inv_obj - - @classmethod - def invalidate(cls, key): - """ - Returns Invalidation object if this given key should be invalidated - None otherwise. `cache_active = False` means that this cache - state is not valid and needs to be invalidated - - :param key: - """ - repo_name = key - repo_name = remove_suffix(repo_name, '_README') - repo_name = remove_suffix(repo_name, '_RSS') - repo_name = remove_suffix(repo_name, '_ATOM') - - # adds instance prefix - key, _prefix, _org_key = cls._get_key(key) - inv = cls._get_or_create_key(key, repo_name) - - if inv and inv.cache_active is False: - return inv - - @classmethod - def set_invalidate(cls, key=None, repo_name=None): - """ - Mark this Cache key for invalidation, either by key or whole - cache sets based on repo_name - - :param key: - """ - if key: - key, _prefix, _org_key = cls._get_key(key) - inv_objs = Session().query(cls).filter(cls.cache_key == key).all() - elif repo_name: - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s', - len(inv_objs), key, repo_name) - try: - for inv_obj in inv_objs: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def set_valid(cls, key): - """ - Mark this cache key as active and currently cached - - :param key: - """ - inv_obj = cls.get_by_key(key) - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - - @classmethod - def get_cache_map(cls): - - class cachemapdict(dict): - - def __init__(self, *args, **kwargs): - fixkey = kwargs.get('fixkey') - if fixkey: - del kwargs['fixkey'] - self.fixkey = fixkey - super(cachemapdict, self).__init__(*args, **kwargs) - - def __getattr__(self, name): - key = name - if self.fixkey: - key, _prefix, _org_key = cls._get_key(key) - if key in self.__dict__: - return self.__dict__[key] - else: - return self[key] - - def __getitem__(self, key): - if self.fixkey: - key, _prefix, _org_key = cls._get_key(key) - try: - return super(cachemapdict, self).__getitem__(key) - except KeyError: - return - - cache_map = cachemapdict(fixkey=True) - for obj in cls.query().all(): - cache_map[obj.cache_key] = cachemapdict(obj.get_dict()) - return cache_map - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' - STATUS_APPROVED = 'approved' - STATUS_REJECTED = 'rejected' - STATUS_UNDER_REVIEW = 'under_review' - - STATUSES = [ - (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default - (STATUS_APPROVED, _("Approved")), - (STATUS_REJECTED, _("Rejected")), - (STATUS_UNDER_REVIEW, _("Under Review")), - ] - - changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - revision = Column('revision', String(40), nullable=False) - status = Column('status', String(128), nullable=False, default=DEFAULT) - changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) - modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) - version = Column('version', Integer(), nullable=False, default=0) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - comment = relationship('ChangesetComment', lazy='joined') - pull_request = relationship('PullRequest', lazy='joined') - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.status, self.author - ) - - @classmethod - def get_status_lbl(cls, value): - return dict(cls.STATUSES).get(value) - - @property - def status_lbl(self): - return ChangesetStatus.get_status_lbl(self.status) - - -class PullRequest(Base, BaseModel): - __tablename__ = 'pull_requests' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - STATUS_NEW = u'new' - STATUS_OPEN = u'open' - STATUS_CLOSED = u'closed' - - pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) - title = Column('title', Unicode(256), nullable=True) - description = Column('description', UnicodeText(10240), nullable=True) - status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max - org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - org_ref = Column('org_ref', Unicode(256), nullable=False) - other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - other_ref = Column('other_ref', Unicode(256), nullable=False) - - @hybrid_property - def revisions(self): - return self._revisions.split(':') - - @revisions.setter - def revisions(self, val): - self._revisions = ':'.join(val) - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') - other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') - statuses = relationship('ChangesetStatus') - comments = relationship('ChangesetComment', - cascade="all, delete, delete-orphan") - - def is_closed(self): - return self.status == self.STATUS_CLOSED - - def __json__(self): - return dict( - revisions=self.revisions - ) - - -class PullRequestReviewers(Base, BaseModel): - __tablename__ = 'pull_request_reviewers' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.model.notification import NotificationModel - return NotificationModel().make_description(self) - - -class UserNotification(Base, BaseModel): - __tablename__ = 'user_to_notification' - __table_args__ = ( - UniqueConstraint('user_id', 'notification_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) - notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) - read = Column('read', Boolean, default=False) - sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) - - user = relationship('User', lazy="joined") - notification = relationship('Notification', lazy="joined", - order_by=lambda: Notification.created_on.desc(),) - - def mark_as_read(self): - self.read = True - Session().add(self) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_5_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_5_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1841 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_5_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea <=1.5.2 - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -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 beaker.cache import cache_region, region_invalidate -from webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_suffix, remove_prefix -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -from kallithea import DB_PREFIX - -URL_SEP = '/' -log = logging.getLogger(__name__) - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - return cls.query().all() - - @classmethod - def delete(cls, id_): - obj = cls.query().get(id_) - Session().delete(obj) - - def __repr__(self): - if hasattr(self, '__unicode__'): - # python repr needs to return str - return safe_str(self.__unicode__()) - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, k='', v=''): - self.app_settings_name = k - self.app_settings_value = v - - @validates('_app_settings_value') - def validate_settings_value(self, key, val): - assert type(val) == unicode - return val - - @hybrid_property - def app_settings_value(self): - v = self._app_settings_value - if self.app_settings_name in ["ldap_active", - "default_repo_enable_statistics", - "default_repo_enable_locking", - "default_repo_private", - "default_repo_enable_downloads"]: - v = str2bool(v) - return v - - @app_settings_value.setter - def app_settings_value(self, val): - """ - Setter that will always make sure we use unicode in app_settings_value - - :param val: - """ - self._app_settings_value = safe_unicode(val) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key): - res = cls.get_by_name(key) - if not res: - res = cls(key) - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - DEFAULT_USER = 'default' - DEFAULT_PERMISSIONS = [ - 'hg.register.manual_activate', 'hg.create.repository', - 'hg.fork.repository', 'repository.read', 'group.read' - ] - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - def get_api_data(self): - """ - Common function for generating user related data for API - """ - user = self - data = dict( - user_id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - emails=user.emails, - api_key=user.api_key, - active=user.active, - admin=user.admin, - ldap_dn=user.ldap_dn, - last_login=user.last_login, - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - __mapper_args__ = {} - - email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - user = relationship('User', lazy='joined') - - @validates('_email') - def validate_email(self, key, email): - # check if this email is not main one - main_email = Session().query(User).filter(User.email == email).scalar() - if main_email is not None: - raise AttributeError('email %s is present is user table' % email) - return email - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', 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) - - 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 UserGroupMember(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('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - - logs = relationship('UserLog') - comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") - - pull_requests_org = relationship('PullRequest', - primaryjoin='PullRequest.org_repo_id==Repository.repo_id', - cascade="all, delete, delete-orphan") - - pull_requests_other = relationship('PullRequest', - primaryjoin='PullRequest.other_repo_id==Repository.repo_id', - cascade="all, delete, delete-orphan") - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, - self.repo_name) - - @hybrid_property - def locked(self): - # always should return [user_id, timelocked] - if self._locked: - _lock_info = self._locked.split(':') - return int(_lock_info[0]), _lock_info[1] - return [None, None] - - @locked.setter - def locked(self, val): - if val and isinstance(val, (list, tuple)): - self._locked = ':'.join(map(str, val)) - else: - self._locked = None - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .options(joinedload(Repository.user))\ - .options(joinedload(Repository.group)) - return q.scalar() - - @classmethod - def get_by_full_path(cls, repo_full_path): - repo_name = repo_full_path.split(cls.base_path(), 1)[-1] - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.ui_key == - Repository.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def repo_full_path(self): - p = [self.repo_path] - # we need to split the name by / since this is how we store the - # names in the database, but that eventually needs to be converted - # into a valid system path - p += self.repo_name.split(Repository.url_sep()) - return os.path.join(*p) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def inject_ui(cls, repo, extras={}): - from kallithea.lib.vcs.backends.hg import MercurialRepository - from kallithea.lib.vcs.backends.git import GitRepository - required = (MercurialRepository, GitRepository) - if not isinstance(repo, required): - raise Exception('repo must be instance of %s' % (','.join(required))) - - # inject ui extra param to log this action via push logger - for k, v in extras.items(): - repo._repo.ui.setconfig('extras', k, v) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None - ) - - return data - - @classmethod - def lock(cls, repo, user_id): - repo.locked = [user_id, time.time()] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @property - def last_db_change(self): - return self.updated_on - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_last_change(self, last_change=None): - if last_change is None: - last_change = datetime.datetime.now() - if self.updated_on is None or self.updated_on != last_change: - log.debug('updated repo %s with new date %s', self, last_change) - self.updated_on = last_change - Session().add(self) - Session().commit() - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - :type revisions: list - """ - - statuses = ChangesetStatus.query()\ - .filter(ChangesetStatus.repo == self)\ - .filter(ChangesetStatus.version == 0) - if revisions: - statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) - grouped = {} - - #maybe we have open new pullrequest without a status ? - stat = ChangesetStatus.STATUS_UNDER_REVIEW - status_lbl = ChangesetStatus.get_status_lbl(stat) - for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): - for rev in pr.revisions: - pr_id = pr.pull_request_id - pr_repo = pr.other_repo.repo_name - grouped[rev] = [stat, status_lbl, pr_id, pr_repo] - - for stat in statuses.all(): - pr_id = pr_repo = None - if stat.pull_request: - pr_id = stat.pull_request.pull_request_id - pr_repo = stat.pull_request.other_repo.repo_name - grouped[stat.revision] = [str(stat.status), stat.status_lbl, - pr_id, pr_repo] - return grouped - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - @property - def invalidate(self): - return CacheInvalidation.invalidate(self.repo_name) - - def set_invalidate(self): - """ - set a cache for invalidation for this instance - """ - CacheInvalidation.set_invalidate(repo_name=self.repo_name) - - @LazyProperty - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, cache_map=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - log.debug('Getting cached instance of repo') - - if cache_map: - # get using prefilled cache_map - invalidate_repo = cache_map[self.repo_name] - if invalidate_repo: - invalidate_repo = (None if invalidate_repo.cache_active - else invalidate_repo) - else: - # get from invalidate - invalidate_repo = self.invalidate - - if invalidate_repo is not None: - region_invalidate(_c, None, rn) - # update our cache - CacheInvalidation.set_valid(invalidate_repo.cache_key) - return _c(rn) - - def __get_instance(self): - repo_full_path = self.repo_full_path - try: - alias = get_scm(repo_full_path)[0] - log.debug('Creating instance of %s repository', alias) - backend = get_backend(alias) - except VCSError: - log.error(traceback.format_exc()) - log.error('Perhaps this repository is in db and not in ' - 'filesystem run rescan repositories with ' - '"destroy old data " option from admin panel') - return - - if alias == 'hg': - - repo = backend(safe_str(repo_full_path), create=False, - baseui=self._ui) - else: - repo = backend(repo_full_path, create=False) - - return repo - - -class RepoGroup(Base, BaseModel): - __tablename__ = 'groups' - __table_args__ = ( - UniqueConstraint('group_name', 'group_parent_id'), - CheckConstraint('group_id != group_parent_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - - parent_group = relationship('RepoGroup', remote_side=group_id) - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, check_perms=False): - from webhelpers.html import literal as _literal - from kallithea.model.scm import ScmModel - groups = cls.query().all() - if check_perms: - #filter group user have access to, it's done - #magically inside ScmModel based on current user - groups = ScmModel().get_repos_groups(groups) - repo_groups = [('', '')] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - all_ = [] - - def _get_members(root_gr): - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - PERMS = [ - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository Group no access')), - ('group.read', _('Repository Group read access')), - ('group.write', _('Repository Group write access')), - ('group.admin', _('Repository Group admin access')), - - ('hg.admin', _('Kallithea 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 Kallithea ' - 'with manual activation')), - - ('hg.register.auto_activate', _('Register new user with Kallithea ' - '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 - } - - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'hg.create.repository', - 'hg.fork.repository', - 'hg.register.manual_activate', - ] - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - repository = relationship('Repository') - permission = relationship('Permission') - - @classmethod - def create(cls, user, repository, permission): - n = cls() - n.user = user - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %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 UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupToPerm(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('UserGroup') - 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 UserGroupRepoGroupToPerm(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('UserGroup') - permission = relationship('Permission') - group = relationship('RepoGroup') - - -class Statistics(Base, BaseModel): - __tablename__ = 'statistics' - __table_args__ = ( - UniqueConstraint('repository_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) - stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) - commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data - commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data - languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data - - repository = relationship('Repository', single_parent=True) - - -class UserFollowing(Base, BaseModel): - __tablename__ = 'user_followings' - __table_args__ = ( - UniqueConstraint('user_id', 'follows_repository_id'), - UniqueConstraint('user_id', 'follows_user_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, cache_args=''): - self.cache_key = cache_key - self.cache_args = cache_args - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, - self.cache_id, self.cache_key) - - @property - def prefix(self): - _split = self.cache_key.split(self.cache_args, 1) - if _split and len(_split) == 2: - return _split[0] - return '' - - @classmethod - def clear_cache(cls): - cls.query().delete() - - @classmethod - def _get_key(cls, key): - """ - Wrapper for generating a key, together with a prefix - - :param key: - """ - import kallithea - prefix = '' - org_key = key - iid = kallithea.CONFIG.get('instance_id') - if iid: - prefix = iid - - return "%s%s" % (prefix, key), prefix, org_key - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.cache_key == key).scalar() - - @classmethod - def get_by_repo_name(cls, repo_name): - return cls.query().filter(cls.cache_args == repo_name).all() - - @classmethod - def _get_or_create_key(cls, key, repo_name, commit=True): - inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() - if not inv_obj: - try: - inv_obj = CacheInvalidation(key, repo_name) - Session().add(inv_obj) - if commit: - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return inv_obj - - @classmethod - def invalidate(cls, key): - """ - Returns Invalidation object if this given key should be invalidated - None otherwise. `cache_active = False` means that this cache - state is not valid and needs to be invalidated - - :param key: - """ - repo_name = key - repo_name = remove_suffix(repo_name, '_README') - repo_name = remove_suffix(repo_name, '_RSS') - repo_name = remove_suffix(repo_name, '_ATOM') - - # adds instance prefix - key, _prefix, _org_key = cls._get_key(key) - inv = cls._get_or_create_key(key, repo_name) - - if inv and inv.cache_active is False: - return inv - - @classmethod - def set_invalidate(cls, key=None, repo_name=None): - """ - Mark this Cache key for invalidation, either by key or whole - cache sets based on repo_name - - :param key: - """ - if key: - key, _prefix, _org_key = cls._get_key(key) - inv_objs = Session().query(cls).filter(cls.cache_key == key).all() - elif repo_name: - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s', - len(inv_objs), key, repo_name) - try: - for inv_obj in inv_objs: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def set_valid(cls, key): - """ - Mark this cache key as active and currently cached - - :param key: - """ - inv_obj = cls.get_by_key(key) - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - - @classmethod - def get_cache_map(cls): - - class cachemapdict(dict): - - def __init__(self, *args, **kwargs): - fixkey = kwargs.get('fixkey') - if fixkey: - del kwargs['fixkey'] - self.fixkey = fixkey - super(cachemapdict, self).__init__(*args, **kwargs) - - def __getattr__(self, name): - key = name - if self.fixkey: - key, _prefix, _org_key = cls._get_key(key) - if key in self.__dict__: - return self.__dict__[key] - else: - return self[key] - - def __getitem__(self, key): - if self.fixkey: - key, _prefix, _org_key = cls._get_key(key) - try: - return super(cachemapdict, self).__getitem__(key) - except KeyError: - return - - cache_map = cachemapdict(fixkey=True) - for obj in cls.query().all(): - cache_map[obj.cache_key] = cachemapdict(obj.get_dict()) - return cache_map - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' - STATUS_APPROVED = 'approved' - STATUS_REJECTED = 'rejected' - STATUS_UNDER_REVIEW = 'under_review' - - STATUSES = [ - (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default - (STATUS_APPROVED, _("Approved")), - (STATUS_REJECTED, _("Rejected")), - (STATUS_UNDER_REVIEW, _("Under Review")), - ] - - changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - revision = Column('revision', String(40), nullable=False) - status = Column('status', String(128), nullable=False, default=DEFAULT) - changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) - modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) - version = Column('version', Integer(), nullable=False, default=0) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - comment = relationship('ChangesetComment', lazy='joined') - pull_request = relationship('PullRequest', lazy='joined') - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.status, self.author - ) - - @classmethod - def get_status_lbl(cls, value): - return dict(cls.STATUSES).get(value) - - @property - def status_lbl(self): - return ChangesetStatus.get_status_lbl(self.status) - - -class PullRequest(Base, BaseModel): - __tablename__ = 'pull_requests' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - STATUS_NEW = u'new' - STATUS_OPEN = u'open' - STATUS_CLOSED = u'closed' - - pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) - title = Column('title', Unicode(256), nullable=True) - description = Column('description', UnicodeText(10240), nullable=True) - status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max - org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - org_ref = Column('org_ref', Unicode(256), nullable=False) - other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - other_ref = Column('other_ref', Unicode(256), nullable=False) - - @hybrid_property - def revisions(self): - return self._revisions.split(':') - - @revisions.setter - def revisions(self, val): - self._revisions = ':'.join(val) - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') - other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') - statuses = relationship('ChangesetStatus') - comments = relationship('ChangesetComment', - cascade="all, delete, delete-orphan") - - def is_closed(self): - return self.status == self.STATUS_CLOSED - - def __json__(self): - return dict( - revisions=self.revisions - ) - - -class PullRequestReviewers(Base, BaseModel): - __tablename__ = 'pull_request_reviewers' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.model.notification import NotificationModel - return NotificationModel().make_description(self) - - -class UserNotification(Base, BaseModel): - __tablename__ = 'user_to_notification' - __table_args__ = ( - UniqueConstraint('user_id', 'notification_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) - notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) - read = Column('read', Boolean, default=False) - sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) - - user = relationship('User', lazy="joined") - notification = relationship('Notification', lazy="joined", - order_by=lambda: Notification.created_on.desc(),) - - def mark_as_read(self): - self.read = True - Session().add(self) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_5_2.py --- a/kallithea/lib/dbmigrate/schema/db_1_5_2.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1962 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_5_2 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea <=1.5.X - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -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 beaker.cache import cache_region, region_invalidate -from webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_suffix, remove_prefix -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - return cls.query().all() - - @classmethod - def delete(cls, id_): - obj = cls.query().get(id_) - Session().delete(obj) - - def __repr__(self): - if hasattr(self, '__unicode__'): - # python repr needs to return str - return safe_str(self.__unicode__()) - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, k='', v=''): - self.app_settings_name = k - self.app_settings_value = v - - @validates('_app_settings_value') - def validate_settings_value(self, key, val): - assert type(val) == unicode - return val - - @hybrid_property - def app_settings_value(self): - v = self._app_settings_value - if self.app_settings_name in ["ldap_active", - "default_repo_enable_statistics", - "default_repo_enable_locking", - "default_repo_private", - "default_repo_enable_downloads"]: - v = str2bool(v) - return v - - @app_settings_value.setter - def app_settings_value(self, val): - """ - Setter that will always make sure we use unicode in app_settings_value - - :param val: - """ - self._app_settings_value = safe_unicode(val) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key): - res = cls.get_by_name(key) - if not res: - res = cls(key) - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - DEFAULT_USER = 'default' - DEFAULT_PERMISSIONS = [ - 'hg.register.manual_activate', 'hg.create.repository', - 'hg.fork.repository', 'repository.read', 'group.read' - ] - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - def get_api_data(self): - """ - Common function for generating user related data for API - """ - user = self - data = dict( - user_id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - emails=user.emails, - api_key=user.api_key, - active=user.active, - admin=user.admin, - ldap_dn=user.ldap_dn, - last_login=user.last_login, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class 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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPv4Network(ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', 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) - - 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 UserGroupMember(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('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - - logs = relationship('UserLog') - 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, - safe_unicode(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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.ui_key == - Repository.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def repo_full_path(self): - p = [self.repo_path] - # we need to split the name by / since this is how we store the - # names in the database, but that eventually needs to be converted - # into a valid system path - p += self.repo_name.split(Repository.url_sep()) - return os.path.join(*p) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def inject_ui(cls, repo, extras={}): - from kallithea.lib.vcs.backends.hg import MercurialRepository - from kallithea.lib.vcs.backends.git import GitRepository - required = (MercurialRepository, GitRepository) - if not isinstance(repo, required): - raise Exception('repo must be instance of %s' % (','.join(required))) - - # inject ui extra param to log this action via push logger - for k, v in extras.items(): - repo._repo.ui.setconfig('extras', k, v) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache - ) - - return data - - @classmethod - def lock(cls, repo, user_id): - repo.locked = [user_id, time.time()] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - :type revisions: list - """ - - statuses = ChangesetStatus.query()\ - .filter(ChangesetStatus.repo == self)\ - .filter(ChangesetStatus.version == 0) - if revisions: - statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) - grouped = {} - - #maybe we have open new pullrequest without a status ? - stat = ChangesetStatus.STATUS_UNDER_REVIEW - status_lbl = ChangesetStatus.get_status_lbl(stat) - for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): - for rev in pr.revisions: - pr_id = pr.pull_request_id - pr_repo = pr.other_repo.repo_name - grouped[rev] = [stat, status_lbl, pr_id, pr_repo] - - for stat in statuses.all(): - pr_id = pr_repo = None - if stat.pull_request: - pr_id = stat.pull_request.pull_request_id - pr_repo = stat.pull_request.other_repo.repo_name - grouped[stat.revision] = [str(stat.status), stat.status_lbl, - pr_id, pr_repo] - return grouped - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - @property - def invalidate(self): - return CacheInvalidation.invalidate(self.repo_name) - - def set_invalidate(self): - """ - set a cache for invalidation for this instance - """ - CacheInvalidation.set_invalidate(repo_name=self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @LazyProperty - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, cache_map=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - log.debug('Getting cached instance of repo') - - if cache_map: - # get using prefilled cache_map - invalidate_repo = cache_map[self.repo_name] - if invalidate_repo: - invalidate_repo = (None if invalidate_repo.cache_active - else invalidate_repo) - else: - # get from invalidate - invalidate_repo = self.invalidate - - if invalidate_repo is not None: - region_invalidate(_c, None, rn) - # update our cache - CacheInvalidation.set_valid(invalidate_repo.cache_key) - return _c(rn) - - def __get_instance(self): - repo_full_path = self.repo_full_path - try: - alias = get_scm(repo_full_path)[0] - log.debug('Creating instance of %s repository', alias) - backend = get_backend(alias) - except VCSError: - log.error(traceback.format_exc()) - log.error('Perhaps this repository is in db and not in ' - 'filesystem run rescan repositories with ' - '"destroy old data " option from admin panel') - return - - if alias == 'hg': - - repo = backend(safe_str(repo_full_path), create=False, - baseui=self._ui) - else: - repo = backend(repo_full_path, create=False) - - return repo - - -class RepoGroup(Base, BaseModel): - __tablename__ = 'groups' - __table_args__ = ( - UniqueConstraint('group_name', 'group_parent_id'), - CheckConstraint('group_id != group_parent_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - - parent_group = relationship('RepoGroup', remote_side=group_id) - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, check_perms=False): - from webhelpers.html import literal as _literal - from kallithea.model.scm import ScmModel - groups = cls.query().all() - if check_perms: - #filter group user have access to, it's done - #magically inside ScmModel based on current user - groups = ScmModel().get_repos_groups(groups) - repo_groups = [('', '')] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - all_ = [] - - def _get_members(root_gr): - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - PERMS = [ - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository Group no access')), - ('group.read', _('Repository Group read access')), - ('group.write', _('Repository Group write access')), - ('group.admin', _('Repository Group admin access')), - - ('hg.admin', _('Kallithea 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 Kallithea ' - 'with manual activation')), - - ('hg.register.auto_activate', _('Register new user with Kallithea ' - 'with auto activation')), - ] - - # defines which permissions are more important higher the more important - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository':1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - repository = relationship('Repository') - permission = relationship('Permission') - - @classmethod - def create(cls, user, repository, permission): - n = cls() - n.user = user - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %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 UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupToPerm(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('UserGroup') - 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 UserGroupRepoGroupToPerm(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('UserGroup') - permission = relationship('Permission') - group = relationship('RepoGroup') - - -class Statistics(Base, BaseModel): - __tablename__ = 'statistics' - __table_args__ = ( - UniqueConstraint('repository_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) - stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) - commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data - commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data - languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data - - repository = relationship('Repository', single_parent=True) - - -class UserFollowing(Base, BaseModel): - __tablename__ = 'user_followings' - __table_args__ = ( - UniqueConstraint('user_id', 'follows_repository_id'), - UniqueConstraint('user_id', 'follows_user_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, cache_args=''): - self.cache_key = cache_key - self.cache_args = cache_args - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s')>" % (self.__class__.__name__, - self.cache_id, self.cache_key) - - @property - def prefix(self): - _split = self.cache_key.split(self.cache_args, 1) - if _split and len(_split) == 2: - return _split[0] - return '' - - @classmethod - def clear_cache(cls): - cls.query().delete() - - @classmethod - def _get_key(cls, key): - """ - Wrapper for generating a key, together with a prefix - - :param key: - """ - import kallithea - prefix = '' - org_key = key - iid = kallithea.CONFIG.get('instance_id') - if iid: - prefix = iid - - return "%s%s" % (prefix, key), prefix, org_key - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.cache_key == key).scalar() - - @classmethod - def get_by_repo_name(cls, repo_name): - return cls.query().filter(cls.cache_args == repo_name).all() - - @classmethod - def _get_or_create_key(cls, key, repo_name, commit=True): - inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() - if not inv_obj: - try: - inv_obj = CacheInvalidation(key, repo_name) - Session().add(inv_obj) - if commit: - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return inv_obj - - @classmethod - def invalidate(cls, key): - """ - Returns Invalidation object if this given key should be invalidated - None otherwise. `cache_active = False` means that this cache - state is not valid and needs to be invalidated - - :param key: - """ - repo_name = key - repo_name = remove_suffix(repo_name, '_README') - repo_name = remove_suffix(repo_name, '_RSS') - repo_name = remove_suffix(repo_name, '_ATOM') - - # adds instance prefix - key, _prefix, _org_key = cls._get_key(key) - inv = cls._get_or_create_key(key, repo_name) - - if inv and inv.cache_active is False: - return inv - - @classmethod - def set_invalidate(cls, key=None, repo_name=None): - """ - Mark this Cache key for invalidation, either by key or whole - cache sets based on repo_name - - :param key: - """ - if key: - key, _prefix, _org_key = cls._get_key(key) - inv_objs = Session().query(cls).filter(cls.cache_key == key).all() - elif repo_name: - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s', - len(inv_objs), key, repo_name) - try: - for inv_obj in inv_objs: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def set_valid(cls, key): - """ - Mark this cache key as active and currently cached - - :param key: - """ - inv_obj = cls.get_by_key(key) - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - - @classmethod - def get_cache_map(cls): - - class cachemapdict(dict): - - def __init__(self, *args, **kwargs): - fixkey = kwargs.get('fixkey') - if fixkey: - del kwargs['fixkey'] - self.fixkey = fixkey - super(cachemapdict, self).__init__(*args, **kwargs) - - def __getattr__(self, name): - key = name - if self.fixkey: - key, _prefix, _org_key = cls._get_key(key) - if key in self.__dict__: - return self.__dict__[key] - else: - return self[key] - - def __getitem__(self, key): - if self.fixkey: - key, _prefix, _org_key = cls._get_key(key) - try: - return super(cachemapdict, self).__getitem__(key) - except KeyError: - return - - cache_map = cachemapdict(fixkey=True) - for obj in cls.query().all(): - cache_map[obj.cache_key] = cachemapdict(obj.get_dict()) - return cache_map - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' - STATUS_APPROVED = 'approved' - STATUS_REJECTED = 'rejected' - STATUS_UNDER_REVIEW = 'under_review' - - STATUSES = [ - (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default - (STATUS_APPROVED, _("Approved")), - (STATUS_REJECTED, _("Rejected")), - (STATUS_UNDER_REVIEW, _("Under Review")), - ] - - changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - revision = Column('revision', String(40), nullable=False) - status = Column('status', String(128), nullable=False, default=DEFAULT) - changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) - modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) - version = Column('version', Integer(), nullable=False, default=0) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - comment = relationship('ChangesetComment', lazy='joined') - pull_request = relationship('PullRequest', lazy='joined') - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.status, self.author - ) - - @classmethod - def get_status_lbl(cls, value): - return dict(cls.STATUSES).get(value) - - @property - def status_lbl(self): - return ChangesetStatus.get_status_lbl(self.status) - - -class PullRequest(Base, BaseModel): - __tablename__ = 'pull_requests' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - STATUS_NEW = u'new' - STATUS_OPEN = u'open' - STATUS_CLOSED = u'closed' - - pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) - title = Column('title', Unicode(256), nullable=True) - description = Column('description', UnicodeText(10240), nullable=True) - status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max - org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - org_ref = Column('org_ref', Unicode(256), nullable=False) - other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - other_ref = Column('other_ref', Unicode(256), nullable=False) - - @hybrid_property - def revisions(self): - return self._revisions.split(':') - - @revisions.setter - def revisions(self, val): - self._revisions = ':'.join(val) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') - other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') - statuses = relationship('ChangesetStatus') - comments = relationship('ChangesetComment', - cascade="all, delete, delete-orphan") - - def is_closed(self): - return self.status == self.STATUS_CLOSED - - def __json__(self): - return dict( - revisions=self.revisions - ) - - -class PullRequestReviewers(Base, BaseModel): - __tablename__ = 'pull_request_reviewers' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.model.notification import NotificationModel - return NotificationModel().make_description(self) - - -class UserNotification(Base, BaseModel): - __tablename__ = 'user_to_notification' - __table_args__ = ( - UniqueConstraint('user_id', 'notification_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) - notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) - read = Column('read', Boolean, default=False) - sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) - - user = relationship('User', lazy="joined") - notification = relationship('Notification', lazy="joined", - order_by=lambda: Notification.created_on.desc(),) - - def mark_as_read(self): - self.read = True - Session().add(self) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_6_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_6_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2041 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_6_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea <=1.5.X - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -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 beaker.cache import cache_region, region_invalidate -from webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_suffix, remove_prefix, time_to_datetime -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - return cls.query().all() - - @classmethod - def delete(cls, id_): - obj = cls.query().get(id_) - Session().delete(obj) - - def __repr__(self): - if hasattr(self, '__unicode__'): - # python repr needs to return str - return safe_str(self.__unicode__()) - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, k='', v=''): - self.app_settings_name = k - self.app_settings_value = v - - @validates('_app_settings_value') - def validate_settings_value(self, key, val): - assert type(val) == unicode - return val - - @hybrid_property - def app_settings_value(self): - v = self._app_settings_value - if self.app_settings_name in ["ldap_active", - "default_repo_enable_statistics", - "default_repo_enable_locking", - "default_repo_private", - "default_repo_enable_downloads"]: - v = str2bool(v) - return v - - @app_settings_value.setter - def app_settings_value(self, val): - """ - Setter that will always make sure we use unicode in app_settings_value - - :param val: - """ - self._app_settings_value = safe_unicode(val) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key): - res = cls.get_by_name(key) - if not res: - res = cls(key) - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - DEFAULT_USER = 'default' - DEFAULT_PERMISSIONS = [ - 'hg.register.manual_activate', 'hg.create.repository', - 'hg.fork.repository', 'repository.read', 'group.read' - ] - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - 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, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class 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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', 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) - - 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 UserGroupMember(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('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - 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() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - :type revisions: list - """ - - statuses = ChangesetStatus.query()\ - .filter(ChangesetStatus.repo == self)\ - .filter(ChangesetStatus.version == 0) - if revisions: - statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) - grouped = {} - - #maybe we have open new pullrequest without a status ? - stat = ChangesetStatus.STATUS_UNDER_REVIEW - status_lbl = ChangesetStatus.get_status_lbl(stat) - for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): - for rev in pr.revisions: - pr_id = pr.pull_request_id - pr_repo = pr.other_repo.repo_name - grouped[rev] = [stat, status_lbl, pr_id, pr_repo] - - for stat in statuses.all(): - pr_id = pr_repo = None - if stat.pull_request: - pr_id = stat.pull_request.pull_request_id - pr_repo = stat.pull_request.other_repo.repo_name - grouped[stat.revision] = [str(stat.status), stat.status_lbl, - pr_id, pr_repo] - return grouped - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - @property - def invalidate(self): - return CacheInvalidation.invalidate(self.repo_name) - - def set_invalidate(self): - """ - set a cache for invalidation for this instance - """ - CacheInvalidation.set_invalidate(repo_name=self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @LazyProperty - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, cache_map=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - log.debug('Getting cached instance of repo') - - if cache_map: - # get using prefilled cache_map - invalidate_repo = cache_map[self.repo_name] - if invalidate_repo: - invalidate_repo = (None if invalidate_repo.cache_active - else invalidate_repo) - else: - # get from invalidate - invalidate_repo = self.invalidate - - if invalidate_repo is not None: - region_invalidate(_c, None, rn) - # update our cache - CacheInvalidation.set_valid(invalidate_repo.cache_key) - return _c(rn) - - def __get_instance(self): - repo_full_path = self.repo_full_path - try: - alias = get_scm(repo_full_path)[0] - log.debug('Creating instance of %s repository from %s', - alias, repo_full_path) - 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) - else: - repo = backend(repo_full_path, create=False) - - return repo - - -class RepoGroup(Base, BaseModel): - __tablename__ = 'groups' - __table_args__ = ( - UniqueConstraint('group_name', 'group_parent_id'), - CheckConstraint('group_id != group_parent_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', 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, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', '-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - PERMS = [ - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('hg.admin', _('Kallithea 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 Kallithea ' - 'with manual activation')), - - ('hg.register.auto_activate', _('Register new user with Kallithea ' - 'with auto activation')), - ] - - # defines which permissions are more important higher the more important - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository':1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - repository = relationship('Repository') - permission = relationship('Permission') - - @classmethod - def create(cls, user, repository, permission): - n = cls() - n.user = user - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %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 UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupToPerm(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('UserGroup') - 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 UserGroupRepoGroupToPerm(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('UserGroup') - permission = relationship('Permission') - group = relationship('RepoGroup') - - -class Statistics(Base, BaseModel): - __tablename__ = 'statistics' - __table_args__ = ( - UniqueConstraint('repository_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) - stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) - commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data - commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data - languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data - - repository = relationship('Repository', single_parent=True) - - -class UserFollowing(Base, BaseModel): - __tablename__ = 'user_followings' - __table_args__ = ( - UniqueConstraint('user_id', 'follows_repository_id'), - UniqueConstraint('user_id', 'follows_user_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # cache_args is usually a repo_name, possibly with _README/_RSS/_ATOM suffix - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, other instances set cache_active to False to invalidate - 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) - - def get_prefix(self): - """ - Guess prefix that might have been used in _get_cache_key to generate self.cache_key . - Only used for informational purposes in repo_edit.html . - """ - _split = self.cache_key.split(self.cache_args, 1) - if len(_split) == 2: - return _split[0] - return '' - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def _get_or_create_inv_obj(cls, key, repo_name, commit=True): - inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() - if not inv_obj: - try: - inv_obj = CacheInvalidation(key, repo_name) - Session().add(inv_obj) - if commit: - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return inv_obj - - @classmethod - def invalidate(cls, key): - """ - Returns Invalidation object if this given key should be invalidated - None otherwise. `cache_active = False` means that this cache - state is not valid and needs to be invalidated - - :param key: - """ - repo_name = key - repo_name = remove_suffix(repo_name, '_README') - repo_name = remove_suffix(repo_name, '_RSS') - repo_name = remove_suffix(repo_name, '_ATOM') - - cache_key = cls._get_cache_key(key) - inv = cls._get_or_create_inv_obj(cache_key, repo_name) - - if inv and not inv.cache_active: - return inv - - @classmethod - def set_invalidate(cls, key=None, repo_name=None): - """ - Mark this Cache key for invalidation, either by key or whole - cache sets based on repo_name - - :param key: - """ - invalidated_keys = [] - if key: - assert not repo_name - cache_key = cls._get_cache_key(key) - inv_objs = Session().query(cls).filter(cls.cache_key == cache_key).all() - else: - assert repo_name - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - try: - for inv_obj in inv_objs: - inv_obj.cache_active = False - log.debug('marking %s key for invalidation based on key=%s,repo_name=%s', - inv_obj, key, safe_str(repo_name)) - invalidated_keys.append(inv_obj.cache_key) - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return invalidated_keys - - @classmethod - def set_valid(cls, key): - """ - Mark this cache key as active and currently cached - - :param key: - """ - inv_obj = cls.query().filter(cls.cache_key == key).scalar() - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - - @classmethod - def get_cache_map(cls): - - class cachemapdict(dict): - - def __init__(self, *args, **kwargs): - self.fixkey = kwargs.pop('fixkey', False) - super(cachemapdict, self).__init__(*args, **kwargs) - - def __getattr__(self, name): - cache_key = name - if self.fixkey: - cache_key = cls._get_cache_key(name) - if cache_key in self.__dict__: - return self.__dict__[cache_key] - else: - return self[cache_key] - - def __getitem__(self, name): - cache_key = name - if self.fixkey: - cache_key = cls._get_cache_key(name) - try: - return super(cachemapdict, self).__getitem__(cache_key) - except KeyError: - return None - - cache_map = cachemapdict(fixkey=True) - for obj in cls.query().all(): - cache_map[obj.cache_key] = cachemapdict(obj.get_dict()) - return cache_map - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' - STATUS_APPROVED = 'approved' - STATUS_REJECTED = 'rejected' - STATUS_UNDER_REVIEW = 'under_review' - - STATUSES = [ - (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default - (STATUS_APPROVED, _("Approved")), - (STATUS_REJECTED, _("Rejected")), - (STATUS_UNDER_REVIEW, _("Under Review")), - ] - - changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - revision = Column('revision', String(40), nullable=False) - status = Column('status', String(128), nullable=False, default=DEFAULT) - changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) - modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) - version = Column('version', Integer(), nullable=False, default=0) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - comment = relationship('ChangesetComment', lazy='joined') - pull_request = relationship('PullRequest', lazy='joined') - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.status, self.author - ) - - @classmethod - def get_status_lbl(cls, value): - return dict(cls.STATUSES).get(value) - - @property - def status_lbl(self): - return ChangesetStatus.get_status_lbl(self.status) - - -class PullRequest(Base, BaseModel): - __tablename__ = 'pull_requests' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - STATUS_NEW = u'new' - STATUS_OPEN = u'open' - STATUS_CLOSED = u'closed' - - pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) - title = Column('title', Unicode(256), nullable=True) - description = Column('description', UnicodeText(10240), nullable=True) - status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max - org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - org_ref = Column('org_ref', Unicode(256), nullable=False) - other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - other_ref = Column('other_ref', Unicode(256), nullable=False) - - @hybrid_property - def revisions(self): - return self._revisions.split(':') - - @revisions.setter - def revisions(self, val): - self._revisions = ':'.join(val) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - def __json__(self): - return dict( - revisions=self.revisions - ) - - -class PullRequestReviewers(Base, BaseModel): - __tablename__ = 'pull_request_reviewers' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.model.notification import NotificationModel - return NotificationModel().make_description(self) - - -class UserNotification(Base, BaseModel): - __tablename__ = 'user_to_notification' - __table_args__ = ( - UniqueConstraint('user_id', 'notification_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) - notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) - read = Column('read', Boolean, default=False) - sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) - - user = relationship('User', lazy="joined") - notification = relationship('Notification', lazy="joined", - order_by=lambda: Notification.created_on.desc(),) - - def mark_as_read(self): - self.read = True - Session().add(self) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_7_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_7_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2223 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_7_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, k='', v=''): - self.app_settings_name = k - self.app_settings_value = v - - @validates('_app_settings_value') - def validate_settings_value(self, key, val): - assert type(val) == unicode - return val - - @hybrid_property - def app_settings_value(self): - v = self._app_settings_value - if self.app_settings_name in ["ldap_active", - "default_repo_enable_statistics", - "default_repo_enable_locking", - "default_repo_private", - "default_repo_enable_downloads"]: - v = str2bool(v) - return v - - @app_settings_value.setter - def app_settings_value(self, val): - """ - Setter that will always make sure we use unicode in app_settings_value - - :param val: - """ - self._app_settings_value = safe_unicode(val) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key): - res = cls.get_by_name(key) - if not res: - res = cls(key) - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - DEFAULT_USER = 'default' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class 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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - self.users_group_name) - - @classmethod - def get_by_group_name(cls, group_name, cache=False, - case_insensitive=False): - if case_insensitive: - q = cls.query().filter(cls.users_group_name.ilike(group_name)) - else: - q = cls.query().filter(cls.users_group_name == group_name) - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(group_name) - ) - ) - return q.scalar() - - @classmethod - def get(cls, users_group_id, cache=False): - users_group = cls.query() - if cache: - users_group = users_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % users_group_id)) - return users_group.get(users_group_id) - - def get_api_data(self): - users_group = self - - data = dict( - users_group_id=users_group.users_group_id, - group_name=users_group.users_group_name, - active=users_group.users_group_active, - ) - - return data - - -class UserGroupMember(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('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - else: - repo = backend(repo_full_path, create=False) - - return repo - - -class RepoGroup(Base, BaseModel): - __tablename__ = 'groups' - __table_args__ = ( - UniqueConstraint('group_name', 'group_parent_id'), - CheckConstraint('group_id != group_parent_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', '-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - 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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(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('UserGroup') - 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 UserGroupRepoGroupToPerm(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('UserGroup') - permission = relationship('Permission') - group = relationship('RepoGroup') - - -class Statistics(Base, BaseModel): - __tablename__ = 'statistics' - __table_args__ = ( - UniqueConstraint('repository_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) - stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) - commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data - commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data - languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data - - repository = relationship('Repository', single_parent=True) - - -class UserFollowing(Base, BaseModel): - __tablename__ = 'user_followings' - __table_args__ = ( - UniqueConstraint('user_id', 'follows_repository_id'), - UniqueConstraint('user_id', 'follows_user_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' - STATUS_APPROVED = 'approved' - STATUS_REJECTED = 'rejected' - STATUS_UNDER_REVIEW = 'under_review' - - STATUSES = [ - (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default - (STATUS_APPROVED, _("Approved")), - (STATUS_REJECTED, _("Rejected")), - (STATUS_UNDER_REVIEW, _("Under Review")), - ] - - changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - revision = Column('revision', String(40), nullable=False) - status = Column('status', String(128), nullable=False, default=DEFAULT) - changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) - modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) - version = Column('version', Integer(), nullable=False, default=0) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - comment = relationship('ChangesetComment', lazy='joined') - pull_request = relationship('PullRequest', lazy='joined') - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.status, self.author - ) - - @classmethod - def get_status_lbl(cls, value): - return dict(cls.STATUSES).get(value) - - @property - def status_lbl(self): - return ChangesetStatus.get_status_lbl(self.status) - - -class PullRequest(Base, BaseModel): - __tablename__ = 'pull_requests' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - STATUS_NEW = u'new' - STATUS_OPEN = u'open' - STATUS_CLOSED = u'closed' - - pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) - title = Column('title', Unicode(256), nullable=True) - description = Column('description', UnicodeText(10240), nullable=True) - status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) - _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max - org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - org_ref = Column('org_ref', Unicode(256), nullable=False) - other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - other_ref = Column('other_ref', Unicode(256), nullable=False) - - @hybrid_property - def revisions(self): - return self._revisions.split(':') - - @revisions.setter - def revisions(self, val): - self._revisions = ':'.join(val) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - def __json__(self): - return dict( - revisions=self.revisions - ) - - -class PullRequestReviewers(Base, BaseModel): - __tablename__ = 'pull_request_reviewers' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8'}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_1_8_0.py --- a/kallithea/lib/dbmigrate/schema/db_1_8_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2270 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_1_8_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(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) - _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, key='', val='', type='unicode'): - self.app_settings_name = key - self.app_settings_value = val - self.app_settings_type = type - - @validates('_app_settings_value') - def validate_settings_value(self, key, val): - assert type(val) == unicode - return val - - @hybrid_property - def app_settings_value(self): - v = self._app_settings_value - if self.app_settings_name in ["ldap_active", - "default_repo_enable_statistics", - "default_repo_enable_locking", - "default_repo_private", - "default_repo_enable_downloads"]: - v = str2bool(v) - return v - - @app_settings_value.setter - def app_settings_value(self, val): - """ - Setter that will always make sure we use unicode in app_settings_value - - :param val: - """ - self._app_settings_value = safe_unicode(val) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key): - res = cls.get_by_name(key) - if not res: - res = cls(key) - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_ldap_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('ldap_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - @classmethod - def get_server_info(cls): - import pkg_resources - import platform - import kallithea - from kallithea.lib.utils import check_git_version - mods = [(p.project_name, p.version) for p in pkg_resources.working_set] - mods += [('git', str(check_git_version()))] - info = { - 'modules': sorted(mods, key=lambda k: k[0].lower()), - 'py_version': platform.python_version(), - 'platform': platform.platform(), - 'kallithea_version': kallithea.__version__ - } - return info - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - # def __init__(self, section='', key='', value=''): - # self.ui_section = section - # self.ui_key = key - # self.ui_value = value - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - DEFAULT_USER = 'default' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - email_id = Column("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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(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) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - 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, user_group_id, cache=False): - user_group = cls.query() - if cache: - user_group = user_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % user_group_id)) - return user_group.get(user_group_id) - - def get_api_data(self, with_members=True): - user_group = self - - data = dict( - users_group_id=user_group.users_group_id, - group_name=user_group.users_group_name, - active=user_group.users_group_active, - owner=user_group.user.username, - ) - if with_members: - members = [] - for user in user_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members - - return data - - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - 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', 'sqlite_autoincrement': True}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', u'-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - def get_api_data(self): - """ - Common function for generating api data - - """ - group = self - data = dict( - group_id=group.group_id, - group_name=group.group_name, - group_description=group.group_description, - parent_group=group.parent_group.group_name if group.parent_group else None, - repositories=[x.repo_name for x in group.repositories], - owner=group.user.username - ) - return data - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - repo_to_perm_id = Column("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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_to_perm_id = Column("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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - users_group_to_perm_id = Column("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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = ( - UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - - 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 UserGroupRepoGroupToPerm(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', 'sqlite_autoincrement': True} - ) - - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True}, - ) - - # values for .status - 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) # only for closedness, not approve/reject/etc - 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) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - 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', 'sqlite_autoincrement': True}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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', 'sqlite_autoincrement': True} - ) - 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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(53), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_2_0_0.py --- a/kallithea/lib/dbmigrate/schema/db_2_0_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2330 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_2_0_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections -import functools - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 Setting(Base, BaseModel): - SETTINGS_TYPES = { - 'str': safe_str, - 'int': safe_int, - 'unicode': safe_unicode, - 'bool': str2bool, - 'list': functools.partial(aslist, sep=',') - } - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, key='', val='', type='unicode'): - self.app_settings_name = key - self.app_settings_value = val - self.app_settings_type = type - - @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 - _type = self.app_settings_type - converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] - return converter(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) - - @hybrid_property - def app_settings_type(self): - return self._app_settings_type - - @app_settings_type.setter - def app_settings_type(self, val): - if val not in self.SETTINGS_TYPES: - raise Exception('type must be one of %s got %s' - % (self.SETTINGS_TYPES.keys(), val)) - self._app_settings_type = val - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value, self.app_settings_type - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key, val='', type='unicode'): - res = cls.get_by_name(key) - if not res: - res = cls(key, val, type) - return res - - @classmethod - def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): - """ - Creates or updates Kallithea setting. If updates is triggered it will only - update parameters that are explicityl set Optional instance will be skipped - - :param key: - :param val: - :param type: - :return: - """ - res = cls.get_by_name(key) - if not res: - val = Optional.extract(val) - type = Optional.extract(type) - res = cls(key, val, type) - else: - res.app_settings_name = key - if not isinstance(val, Optional): - # update if set - res.app_settings_value = val - if not isinstance(type, Optional): - # update if set - res.app_settings_type = type - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_auth_plugins(cls, cache=False): - auth_plugins = cls.get_by_name("auth_plugins").app_settings_value - return auth_plugins - - @classmethod - def get_auth_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('auth_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - @classmethod - def get_server_info(cls): - import pkg_resources - import platform - import kallithea - from kallithea.lib.utils import check_git_version - mods = [(p.project_name, p.version) for p in pkg_resources.working_set] - info = { - 'modules': sorted(mods, key=lambda k: k[0].lower()), - 'py_version': platform.python_version(), - 'platform': platform.platform(), - 'kallithea_version': kallithea.__version__, - 'git_version': str(check_git_version()), - 'git_path': kallithea.CONFIG.get('git_path') - } - return info - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - # def __init__(self, section='', key='', value=''): - # self.ui_section = section - # self.ui_key = key - # self.ui_value = value - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - DEFAULT_USER = 'default' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - #for migration reasons, this is going to be later deleted - 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) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - extern_type=user.extern_type, - extern_name=user.extern_name, - last_login=user.last_login, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - email_id = Column("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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, 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) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - # don't trigger lazy load for migrations - #members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - 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, user_group_id, cache=False): - user_group = cls.query() - if cache: - user_group = user_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % user_group_id)) - return user_group.get(user_group_id) - - def get_api_data(self, with_members=True): - user_group = self - - data = dict( - users_group_id=user_group.users_group_id, - group_name=user_group.users_group_name, - group_description=user_group.user_group_description, - active=user_group.users_group_active, - owner=user_group.user.username, - ) - if with_members: - members = [] - for user in user_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members - - return data - - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - 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', 'sqlite_autoincrement': True}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', u'-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - def get_api_data(self): - """ - Common function for generating api data - - """ - group = self - data = dict( - group_id=group.group_id, - group_name=group.group_name, - group_description=group.group_description, - parent_group=group.parent_group.group_name if group.parent_group else None, - repositories=[x.repo_name for x in group.repositories], - owner=group.user.username - ) - return data - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - repo_to_perm_id = Column("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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_to_perm_id = Column("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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - users_group_to_perm_id = Column("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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = ( - UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - - 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 UserGroupRepoGroupToPerm(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', 'sqlite_autoincrement': True} - ) - - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name, delete=False): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - if delete: - Session().delete(inv_obj) - else: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True}, - ) - - # values for .status - 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) # only for closedness, not approve/reject/etc - 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) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - 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', 'sqlite_autoincrement': True}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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', 'sqlite_autoincrement': True} - ) - 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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(53), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_2_0_1.py --- a/kallithea/lib/dbmigrate/schema/db_2_0_1.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2331 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_2_0_1 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections -import functools - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -from kallithea import DB_PREFIX - -URL_SEP = '/' -log = logging.getLogger(__name__) - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 - try: - return safe_str(self.__unicode__()) - except UnicodeDecodeError: - pass - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - SETTINGS_TYPES = { - 'str': safe_str, - 'int': safe_int, - 'unicode': safe_unicode, - 'bool': str2bool, - 'list': functools.partial(aslist, sep=',') - } - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, key='', val='', type='unicode'): - self.app_settings_name = key - self.app_settings_value = val - self.app_settings_type = type - - @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 - _type = self.app_settings_type - converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] - return converter(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) - - @hybrid_property - def app_settings_type(self): - return self._app_settings_type - - @app_settings_type.setter - def app_settings_type(self, val): - if val not in self.SETTINGS_TYPES: - raise Exception('type must be one of %s got %s' - % (self.SETTINGS_TYPES.keys(), val)) - self._app_settings_type = val - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value, self.app_settings_type - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key, val='', type='unicode'): - res = cls.get_by_name(key) - if not res: - res = cls(key, val, type) - return res - - @classmethod - def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): - """ - Creates or updates Kallithea setting. If updates is triggered it will only - update parameters that are explicityl set Optional instance will be skipped - - :param key: - :param val: - :param type: - :return: - """ - res = cls.get_by_name(key) - if not res: - val = Optional.extract(val) - type = Optional.extract(type) - res = cls(key, val, type) - else: - res.app_settings_name = key - if not isinstance(val, Optional): - # update if set - res.app_settings_value = val - if not isinstance(type, Optional): - # update if set - res.app_settings_type = type - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_auth_plugins(cls, cache=False): - auth_plugins = cls.get_by_name("auth_plugins").app_settings_value - return auth_plugins - - @classmethod - def get_auth_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('auth_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - @classmethod - def get_server_info(cls): - import pkg_resources - import platform - import kallithea - from kallithea.lib.utils import check_git_version - mods = [(p.project_name, p.version) for p in pkg_resources.working_set] - info = { - 'modules': sorted(mods, key=lambda k: k[0].lower()), - 'py_version': platform.python_version(), - 'platform': safe_unicode(platform.platform()), - 'kallithea_version': kallithea.__version__, - 'git_version': safe_unicode(check_git_version()), - 'git_path': kallithea.CONFIG.get('git_path') - } - return info - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - # def __init__(self, section='', key='', value=''): - # self.ui_section = section - # self.ui_key = key - # self.ui_value = value - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - DEFAULT_USER = 'default' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - extern_name = Column("extern_name", 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) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - extern_type=user.extern_type, - extern_name=user.extern_name, - last_login=user.last_login, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - email_id = Column("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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, 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) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - 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, user_group_id, cache=False): - user_group = cls.query() - if cache: - user_group = user_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % user_group_id)) - return user_group.get(user_group_id) - - def get_api_data(self, with_members=True): - user_group = self - - data = dict( - users_group_id=user_group.users_group_id, - group_name=user_group.users_group_name, - group_description=user_group.user_group_description, - active=user_group.users_group_active, - owner=user_group.user.username, - ) - if with_members: - members = [] - for user in user_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members - - return data - - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - cs = self.get_changeset(self.landing_rev) or self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - 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', 'sqlite_autoincrement': True}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - #TODO: create this field in migrations - #created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', u'-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - def get_api_data(self): - """ - Common function for generating api data - - """ - group = self - data = dict( - group_id=group.group_id, - group_name=group.group_name, - group_description=group.group_description, - parent_group=group.parent_group.group_name if group.parent_group else None, - repositories=[x.repo_name for x in group.repositories], - owner=group.user.username - ) - return data - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - repo_to_perm_id = Column("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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_to_perm_id = Column("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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - users_group_to_perm_id = Column("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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = ( - UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - - 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 UserGroupRepoGroupToPerm(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', 'sqlite_autoincrement': True} - ) - - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name, delete=False): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - if delete: - Session().delete(inv_obj) - else: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True}, - ) - - # values for .status - 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) # only for closedness, not approve/reject/etc - 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) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - 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', 'sqlite_autoincrement': True}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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', 'sqlite_autoincrement': True} - ) - 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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(53), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_2_0_2.py --- a/kallithea/lib/dbmigrate/schema/db_2_0_2.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2352 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_2_0_2 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections -import functools - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 - try: - return safe_str(self.__unicode__()) - except UnicodeDecodeError: - pass - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - SETTINGS_TYPES = { - 'str': safe_str, - 'int': safe_int, - 'unicode': safe_unicode, - 'bool': str2bool, - 'list': functools.partial(aslist, sep=',') - } - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, key='', val='', type='unicode'): - self.app_settings_name = key - self.app_settings_value = val - self.app_settings_type = type - - @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 - _type = self.app_settings_type - converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] - return converter(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) - - @hybrid_property - def app_settings_type(self): - return self._app_settings_type - - @app_settings_type.setter - def app_settings_type(self, val): - if val not in self.SETTINGS_TYPES: - raise Exception('type must be one of %s got %s' - % (self.SETTINGS_TYPES.keys(), val)) - self._app_settings_type = val - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value, self.app_settings_type - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key, val='', type='unicode'): - res = cls.get_by_name(key) - if not res: - res = cls(key, val, type) - return res - - @classmethod - def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): - """ - Creates or updates Kallithea setting. If updates is triggered it will only - update parameters that are explicityl set Optional instance will be skipped - - :param key: - :param val: - :param type: - :return: - """ - res = cls.get_by_name(key) - if not res: - val = Optional.extract(val) - type = Optional.extract(type) - res = cls(key, val, type) - else: - res.app_settings_name = key - if not isinstance(val, Optional): - # update if set - res.app_settings_value = val - if not isinstance(type, Optional): - # update if set - res.app_settings_type = type - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_auth_plugins(cls, cache=False): - auth_plugins = cls.get_by_name("auth_plugins").app_settings_value - return auth_plugins - - @classmethod - def get_auth_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('auth_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - @classmethod - def get_server_info(cls): - import pkg_resources - import platform - import kallithea - from kallithea.lib.utils import check_git_version - mods = [(p.project_name, p.version) for p in pkg_resources.working_set] - info = { - 'modules': sorted(mods, key=lambda k: k[0].lower()), - 'py_version': platform.python_version(), - 'platform': safe_unicode(platform.platform()), - 'kallithea_version': kallithea.__version__, - 'git_version': safe_unicode(check_git_version()), - 'git_path': kallithea.CONFIG.get('git_path') - } - return info - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - # def __init__(self, section='', key='', value=''): - # self.ui_section = section - # self.ui_key = key - # self.ui_value = value - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '' % (self.__class__.__name__, self.ui_key, - self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - DEFAULT_USER = 'default' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - extern_name = Column("extern_name", 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) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - return q.scalar() - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - extern_type=user.extern_type, - extern_name=user.extern_name, - last_login=user.last_login, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - email_id = Column("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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, 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) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - 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, user_group_id, cache=False): - user_group = cls.query() - if cache: - user_group = user_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % user_group_id)) - return user_group.get(user_group_id) - - def get_api_data(self, with_members=True): - user_group = self - - data = dict( - users_group_id=user_group.users_group_id, - group_name=user_group.users_group_name, - group_description=user_group.user_group_description, - active=user_group.users_group_active, - owner=user_group.user.username, - ) - if with_members: - members = [] - for user in user_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members - - return data - - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(self.repo_name)) - - @hybrid_property - def landing_rev(self): - # always should return [rev_type, rev] - if self._landing_revision: - _rev_info = self._landing_revision.split(':') - if len(_rev_info) < 2: - _rev_info.insert(0, 'rev') - return [_rev_info[0], _rev_info[1]] - return [None, None] - - @landing_rev.setter - def landing_rev(self, val): - if ':' not in val: - raise ValueError('value must be delimited with `:` and consist ' - 'of :, got %s instead' % val) - self._landing_revision = val - - @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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - 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 - """ - _rev_type, _rev = self.landing_rev - cs = self.get_changeset(_rev) - if isinstance(cs, EmptyChangeset): - return self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - else: - repo = backend(repo_full_path, create=False) - - return repo - - def __json__(self): - return dict(landing_rev = self.landing_rev) - -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', 'sqlite_autoincrement': True}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', u'-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - def get_api_data(self): - """ - Common function for generating api data - - """ - group = self - data = dict( - group_id=group.group_id, - group_name=group.group_name, - group_description=group.group_description, - parent_group=group.parent_group.group_name if group.parent_group else None, - repositories=[x.repo_name for x in group.repositories], - owner=group.user.username - ) - return data - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - repo_to_perm_id = Column("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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_to_perm_id = Column("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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - users_group_to_perm_id = Column("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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = ( - UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - - 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 UserGroupRepoGroupToPerm(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', 'sqlite_autoincrement': True} - ) - - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name, delete=False): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - if delete: - Session().delete(inv_obj) - else: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True}, - ) - - # values for .status - 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) # only for closedness, not approve/reject/etc - 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) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - 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', 'sqlite_autoincrement': True}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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', 'sqlite_autoincrement': True} - ) - 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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(53), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_2_1_0.py --- a/kallithea/lib/dbmigrate/schema/db_2_1_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2391 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_2_1_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections -import functools - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 - try: - return safe_str(self.__unicode__()) - except UnicodeDecodeError: - pass - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - SETTINGS_TYPES = { - 'str': safe_str, - 'int': safe_int, - 'unicode': safe_unicode, - 'bool': str2bool, - 'list': functools.partial(aslist, sep=',') - } - DEFAULT_UPDATE_URL = '' - - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __init__(self, key='', val='', type='unicode'): - self.app_settings_name = key - self.app_settings_value = val - self.app_settings_type = type - - @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 - _type = self.app_settings_type - converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] - return converter(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) - - @hybrid_property - def app_settings_type(self): - return self._app_settings_type - - @app_settings_type.setter - def app_settings_type(self, val): - if val not in self.SETTINGS_TYPES: - raise Exception('type must be one of %s got %s' - % (self.SETTINGS_TYPES.keys(), val)) - self._app_settings_type = val - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value, self.app_settings_type - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key, val='', type='unicode'): - res = cls.get_by_name(key) - if not res: - res = cls(key, val, type) - return res - - @classmethod - def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): - """ - Creates or updates Kallithea setting. If updates is triggered it will only - update parameters that are explicityl set Optional instance will be skipped - - :param key: - :param val: - :param type: - :return: - """ - res = cls.get_by_name(key) - if not res: - val = Optional.extract(val) - type = Optional.extract(type) - res = cls(key, val, type) - else: - res.app_settings_name = key - if not isinstance(val, Optional): - # update if set - res.app_settings_value = val - if not isinstance(type, Optional): - # update if set - res.app_settings_type = type - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_auth_plugins(cls, cache=False): - auth_plugins = cls.get_by_name("auth_plugins").app_settings_value - return auth_plugins - - @classmethod - def get_auth_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('auth_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - @classmethod - def get_server_info(cls): - import pkg_resources - import platform - import kallithea - from kallithea.lib.utils import check_git_version - mods = [(p.project_name, p.version) for p in pkg_resources.working_set] - info = { - 'modules': sorted(mods, key=lambda k: k[0].lower()), - 'py_version': platform.python_version(), - 'platform': safe_unicode(platform.platform()), - 'kallithea_version': kallithea.__version__, - 'git_version': safe_unicode(check_git_version()), - 'git_path': kallithea.CONFIG.get('git_path') - } - return info - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - # def __init__(self, section='', key='', value=''): - # self.ui_section = section - # self.ui_key = key - # self.ui_value = value - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section, - self.ui_key, self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - DEFAULT_USER = 'default' - DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - extern_name = Column("extern_name", 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) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - 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, fallback=True): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - res = q.scalar() - - if fallback and not res: - #fallback to additional keys - _res = UserApiKeys.query()\ - .filter(UserApiKeys.api_key == api_key)\ - .filter(or_(UserApiKeys.expires == -1, - UserApiKeys.expires >= time.time()))\ - .first() - if _res: - res = _res.user - return res - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - extern_type=user.extern_type, - extern_name=user.extern_name, - last_login=user.last_login, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserApiKeys(Base, BaseModel): - __tablename__ = 'user_api_keys' - __table_args__ = ( - Index('uak_api_key_idx', 'api_key'), - UniqueConstraint('api_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - user_api_key_id = Column("user_api_key_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) - api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True) - description = Column('description', UnicodeText(1024)) - expires = Column('expires', Float(53), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - user = relationship('User', lazy='joined') - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - email_id = Column("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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - def __unicode__(self): - return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__, - self.user_id, self.ip_addr) - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, 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) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - 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, user_group_id, cache=False): - user_group = cls.query() - if cache: - user_group = user_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % user_group_id)) - return user_group.get(user_group_id) - - def get_api_data(self, with_members=True): - user_group = self - - data = dict( - users_group_id=user_group.users_group_id, - group_name=user_group.users_group_name, - group_description=user_group.user_group_description, - active=user_group.users_group_active, - owner=user_group.user.username, - ) - if with_members: - members = [] - for user in user_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members - - return data - - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) - field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}' - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(self.repo_name)) - - @hybrid_property - def landing_rev(self): - # always should return [rev_type, rev] - if self._landing_revision: - _rev_info = self._landing_revision.split(':') - if len(_rev_info) < 2: - _rev_info.insert(0, 'rev') - return [_rev_info[0], _rev_info[1]] - return [None, None] - - @landing_rev.setter - def landing_rev(self, val): - if ':' not in val: - raise ValueError('value must be delimited with `:` and consist ' - 'of :, got %s instead' % val) - self._landing_revision = val - - @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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - from urlparse import urlparse - import urllib - parsed_url = urlparse(h.canonical_url('home')) - default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' - decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) - args = { - 'user': '', - 'pass': '', - 'scheme': parsed_url.scheme, - 'netloc': parsed_url.netloc, - 'prefix': decoded_path, - 'path': self.repo_name - } - - args.update(override) - return default_clone_uri % args - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - 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 - """ - _rev_type, _rev = self.landing_rev - cs = self.get_changeset(_rev) - if isinstance(cs, EmptyChangeset): - return self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - else: - repo = backend(repo_full_path, create=False) - - return repo - - def __json__(self): - return dict(landing_rev = self.landing_rev) - -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', 'sqlite_autoincrement': True}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - from webhelpers.html import literal as _literal - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', u'-- %s --' % _('top level'))] - sep = ' » ' - _name = lambda k: _literal(sep.join(k)) - - repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) - for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 5 - groups = [] - if self.parent_group is None: - return groups - cur_gr = self.parent_group - groups.insert(0, cur_gr) - cnt = 0 - while 1: - cnt += 1 - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - if cnt == parents_recursion_limit: - # this will prevent accidental infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - def get_api_data(self): - """ - Common function for generating api data - - """ - group = self - data = dict( - group_id=group.group_id, - group_name=group.group_name, - group_description=group.group_description, - parent_group=group.parent_group.group_name if group.parent_group else None, - repositories=[x.repo_name for x in group.repositories], - owner=group.user.username - ) - return data - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - repo_to_perm_id = Column("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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_to_perm_id = Column("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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - users_group_to_perm_id = Column("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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = ( - UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - - 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 UserGroupRepoGroupToPerm(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', 'sqlite_autoincrement': True} - ) - - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name, delete=False): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - if delete: - Session().delete(inv_obj) - else: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True}, - ) - - # values for .status - 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) # only for closedness, not approve/reject/etc - 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) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - 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', 'sqlite_autoincrement': True}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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', 'sqlite_autoincrement': True} - ) - 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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(53), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_2_2_0.py --- a/kallithea/lib/dbmigrate/schema/db_2_2_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2448 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_2_2_0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections -import functools - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \ - get_clone_url -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 - try: - return safe_str(self.__unicode__()) - except UnicodeDecodeError: - pass - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - SETTINGS_TYPES = { - 'str': safe_str, - 'int': safe_int, - 'unicode': safe_unicode, - 'bool': str2bool, - 'list': functools.partial(aslist, sep=',') - } - DEFAULT_UPDATE_URL = '' - - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None) - _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - - def __init__(self, key='', val='', type='unicode'): - self.app_settings_name = key - self.app_settings_value = val - self.app_settings_type = type - - @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 - _type = self.app_settings_type - converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] - return converter(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) - - @hybrid_property - def app_settings_type(self): - return self._app_settings_type - - @app_settings_type.setter - def app_settings_type(self, val): - if val not in self.SETTINGS_TYPES: - raise Exception('type must be one of %s got %s' - % (self.SETTINGS_TYPES.keys(), val)) - self._app_settings_type = val - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value, self.app_settings_type - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key, val='', type='unicode'): - res = cls.get_by_name(key) - if not res: - res = cls(key, val, type) - return res - - @classmethod - def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): - """ - Creates or updates Kallithea setting. If updates is triggered it will only - update parameters that are explicityl set Optional instance will be skipped - - :param key: - :param val: - :param type: - :return: - """ - res = cls.get_by_name(key) - if not res: - val = Optional.extract(val) - type = Optional.extract(type) - res = cls(key, val, type) - else: - res.app_settings_name = key - if not isinstance(val, Optional): - # update if set - res.app_settings_value = val - if not isinstance(type, Optional): - # update if set - res.app_settings_type = type - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_auth_plugins(cls, cache=False): - auth_plugins = cls.get_by_name("auth_plugins").app_settings_value - return auth_plugins - - @classmethod - def get_auth_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('auth_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - @classmethod - def get_server_info(cls): - import pkg_resources - import platform - import kallithea - from kallithea.lib.utils import check_git_version - mods = [(p.project_name, p.version) for p in pkg_resources.working_set] - info = { - 'modules': sorted(mods, key=lambda k: k[0].lower()), - 'py_version': platform.python_version(), - 'platform': safe_unicode(platform.platform()), - 'kallithea_version': kallithea.__version__, - 'git_version': safe_unicode(check_git_version()), - 'git_path': kallithea.CONFIG.get('git_path') - } - return info - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - # def __init__(self, section='', key='', value=''): - # self.ui_section = section - # self.ui_key = key - # self.ui_value = value - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section, - self.ui_key, self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - DEFAULT_USER = 'default' - DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - #_user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - #extra API keys - user_api_keys = relationship('UserApiKeys', cascade='all') - - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def api_keys(self): - other = UserApiKeys.query().filter(UserApiKeys.user==self).all() - return [self.api_key] + [x.api_key for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - @hybrid_property - def user_data(self): - if not self._user_data: - return {} - - try: - return json.loads(self._user_data) - except TypeError: - return {} - - @user_data.setter - def user_data(self, val): - try: - self._user_data = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False, fallback=True): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - res = q.scalar() - - if fallback and not res: - #fallback to additional keys - _res = UserApiKeys.query()\ - .filter(UserApiKeys.api_key == api_key)\ - .filter(or_(UserApiKeys.expires == -1, - UserApiKeys.expires >= time.time()))\ - .first() - if _res: - res = _res.user - return res - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - api_keys=user.api_keys, - active=user.active, - admin=user.admin, - extern_type=user.extern_type, - extern_name=user.extern_name, - last_login=user.last_login, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserApiKeys(Base, BaseModel): - __tablename__ = 'user_api_keys' - __table_args__ = ( - Index('uak_api_key_idx', 'api_key'), - Index('uak_api_key_expires_idx', 'api_key', 'expires'), - UniqueConstraint('api_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - user_api_key_id = Column("user_api_key_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) - api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True) - description = Column('description', UnicodeText(1024)) - expires = Column('expires', Float(53), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - user = relationship('User', lazy='joined') - - @property - def expired(self): - if self.expires == -1: - return False - return time.time() > self.expires - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - email_id = Column("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), 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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - def __unicode__(self): - return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__, - self.user_id, self.ip_addr) - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) - user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, 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) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - 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, user_group_id, cache=False): - user_group = cls.query() - if cache: - user_group = user_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % user_group_id)) - return user_group.get(user_group_id) - - def get_api_data(self, with_members=True): - user_group = self - - data = dict( - users_group_id=user_group.users_group_id, - group_name=user_group.users_group_name, - group_description=user_group.user_group_description, - active=user_group.users_group_active, - owner=user_group.user.username, - ) - if with_members: - members = [] - for user in user_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members - - return data - - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False)) - field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}' - DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}' - - STATE_CREATED = 'repo_state_created' - STATE_PENDING = 'repo_state_pending' - STATE_ERROR = 'repo_state_error' - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - _landing_revision = Column("landing_revision", String(255, convert_unicode=False), 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), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(self.repo_name)) - - @hybrid_property - def landing_rev(self): - # always should return [rev_type, rev] - if self._landing_revision: - _rev_info = self._landing_revision.split(':') - if len(_rev_info) < 2: - _rev_info.insert(0, 'rev') - return [_rev_info[0], _rev_info[1]] - return [None, None] - - @landing_rev.setter - def landing_rev(self, val): - if ':' not in val: - raise ValueError('value must be delimited with `:` and consist ' - 'of :, got %s instead' % val) - self._landing_revision = val - - @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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - qualified_home_url = h.canonical_url('home') - - uri_tmpl = None - if 'uri_tmpl' in override: - uri_tmpl = override['uri_tmpl'] - del override['uri_tmpl'] - - # we didn't override our tmpl from **overrides - if not uri_tmpl: - uri_tmpl = self.DEFAULT_CLONE_URI - try: - from pylons import tmpl_context as c - uri_tmpl = c.clone_uri_tmpl - except Exception: - # in any case if we call this outside of request context, - # ie, not having tmpl_context set up - pass - - return get_clone_url(uri_tmpl=uri_tmpl, - qualified_home_url=qualified_home_url, - repo_name=self.repo_name, - repo_id=self.repo_id, **override) - - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - _rev_type, _rev = self.landing_rev - cs = self.get_changeset(_rev) - if isinstance(cs, EmptyChangeset): - return self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - else: - repo = backend(repo_full_path, create=False) - - return repo - - def __json__(self): - return dict(landing_rev = self.landing_rev) - -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', 'sqlite_autoincrement': True}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - SEP = ' » ' - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def _generate_choice(cls, repo_group): - from webhelpers.html import literal as _literal - _name = lambda k: _literal(cls.SEP.join(k)) - return repo_group.group_id, _name(repo_group.full_path_splitted) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', u'-- %s --' % _('top level'))] - - repo_groups.extend([cls._generate_choice(x) for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 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 infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - def get_api_data(self): - """ - Common function for generating api data - - """ - group = self - data = dict( - group_id=group.group_id, - group_name=group.group_name, - group_description=group.group_description, - parent_group=group.parent_group.group_name if group.parent_group else None, - repositories=[x.repo_name for x in group.repositories], - owner=group.user.username - ) - return data - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')), - ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.create.write_on_repogroup.true', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - repo_to_perm_id = Column("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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_to_perm_id = Column("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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - users_group_to_perm_id = Column("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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = ( - UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - - 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 UserGroupRepoGroupToPerm(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', 'sqlite_autoincrement': True} - ) - - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name, delete=False): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - log.debug('for repo %s got %s invalidation objects', repo_name, inv_objs) - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - if delete: - Session().delete(inv_obj) - else: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True}, - ) - - # values for .status - 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) # only for closedness, not approve/reject/etc - 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) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - 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', 'sqlite_autoincrement': True}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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', 'sqlite_autoincrement': True} - ) - 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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - DEFAULT_FILENAME = u'gistfile1.txt' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(53), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - def __repr__(self): - return '' % (self.gist_type, self.gist_access_id) - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/schema/db_2_2_3.py --- a/kallithea/lib/dbmigrate/schema/db_2_2_3.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2494 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.schema.db_2_2_3 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Database Models for Kallithea - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Apr 08, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -import os -import time -import logging -import datetime -import traceback -import hashlib -import collections -import functools - -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 webob.exc import HTTPNotFound - -from pylons.i18n.translation import lazy_ugettext as _ - -from kallithea.lib.vcs import get_backend -from kallithea.lib.vcs.utils.helpers import get_scm -from kallithea.lib.vcs.exceptions import VCSError -from kallithea.lib.vcs.utils.lazy import LazyProperty -from kallithea.lib.vcs.backends.base import EmptyChangeset - -from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ - safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \ - get_clone_url -from kallithea.lib.compat import json -from kallithea.lib.caching_query import FromCache - -from kallithea.model.meta import Base, Session - -URL_SEP = '/' -log = logging.getLogger(__name__) - -from kallithea import DB_PREFIX - -#============================================================================== -# BASE CLASSES -#============================================================================== - -_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() - - -class BaseModel(object): - """ - Base Model for all classess - """ - - @classmethod - def _get_keys(cls): - """return column names for this model """ - return class_mapper(cls).c.keys() - - def get_dict(self): - """ - return dict with keys and values corresponding - to this model data """ - - d = {} - for k in self._get_keys(): - d[k] = getattr(self, k) - - # also use __json__() if present to get additional fields - _json_attr = getattr(self, '__json__', None) - if _json_attr: - # update with attributes from __json__ - if callable(_json_attr): - _json_attr = _json_attr() - for k, val in _json_attr.iteritems(): - d[k] = val - return d - - def get_appstruct(self): - """return list with keys and values tuples corresponding - to this model data """ - - l = [] - for k in self._get_keys(): - l.append((k, getattr(self, k),)) - return l - - def populate_obj(self, populate_dict): - """populate model with data from given populate_dict""" - - for k in self._get_keys(): - if k in populate_dict: - setattr(self, k, populate_dict[k]) - - @classmethod - def query(cls): - return Session().query(cls) - - @classmethod - def get(cls, id_): - if id_: - return cls.query().get(id_) - - @classmethod - def get_or_404(cls, id_): - try: - id_ = int(id_) - except (TypeError, ValueError): - raise HTTPNotFound - - res = cls.query().get(id_) - if not res: - raise HTTPNotFound - return res - - @classmethod - def getAll(cls): - # deprecated and left for backward compatibility - return cls.get_all() - - @classmethod - def get_all(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 - try: - return safe_str(self.__unicode__()) - except UnicodeDecodeError: - pass - return '' % (self.__class__.__name__) - - -class Setting(Base, BaseModel): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = ( - UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - SETTINGS_TYPES = { - 'str': safe_str, - 'int': safe_int, - 'unicode': safe_unicode, - 'bool': str2bool, - 'list': functools.partial(aslist, sep=',') - } - DEFAULT_UPDATE_URL = '' - - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None) - _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - - def __init__(self, key='', val='', type='unicode'): - self.app_settings_name = key - self.app_settings_value = val - self.app_settings_type = type - - @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 - _type = self.app_settings_type - converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] - return converter(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) - - @hybrid_property - def app_settings_type(self): - return self._app_settings_type - - @app_settings_type.setter - def app_settings_type(self, val): - if val not in self.SETTINGS_TYPES: - raise Exception('type must be one of %s got %s' - % (self.SETTINGS_TYPES.keys(), val)) - self._app_settings_type = val - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % ( - self.__class__.__name__, - self.app_settings_name, self.app_settings_value, self.app_settings_type - ) - - @classmethod - def get_by_name(cls, key): - return cls.query()\ - .filter(cls.app_settings_name == key).scalar() - - @classmethod - def get_by_name_or_create(cls, key, val='', type='unicode'): - res = cls.get_by_name(key) - if not res: - res = cls(key, val, type) - return res - - @classmethod - def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): - """ - Creates or updates Kallithea setting. If updates is triggered it will only - update parameters that are explicityl set Optional instance will be skipped - - :param key: - :param val: - :param type: - :return: - """ - res = cls.get_by_name(key) - if not res: - val = Optional.extract(val) - type = Optional.extract(type) - res = cls(key, val, type) - else: - res.app_settings_name = key - if not isinstance(val, Optional): - # update if set - res.app_settings_value = val - if not isinstance(type, Optional): - # update if set - res.app_settings_type = type - return res - - @classmethod - def get_app_settings(cls, cache=False): - - ret = cls.query() - - if cache: - ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) - - if not ret: - raise Exception('Could not get application settings !') - settings = {} - for each in ret: - settings[each.app_settings_name] = \ - each.app_settings_value - - return settings - - @classmethod - def get_auth_plugins(cls, cache=False): - auth_plugins = cls.get_by_name("auth_plugins").app_settings_value - return auth_plugins - - @classmethod - def get_auth_settings(cls, cache=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('auth_')).all() - fd = {} - for row in ret: - fd.update({row.app_settings_name: row.app_settings_value}) - - return fd - - @classmethod - def get_default_repo_settings(cls, cache=False, strip_prefix=False): - ret = cls.query()\ - .filter(cls.app_settings_name.startswith('default_')).all() - fd = {} - for row in ret: - key = row.app_settings_name - if strip_prefix: - key = remove_prefix(key, prefix='default_') - fd.update({key: row.app_settings_value}) - - return fd - - @classmethod - def get_server_info(cls): - import pkg_resources - import platform - import kallithea - from kallithea.lib.utils import check_git_version - mods = [(p.project_name, p.version) for p in pkg_resources.working_set] - info = { - 'modules': sorted(mods, key=lambda k: k[0].lower()), - 'py_version': platform.python_version(), - 'platform': safe_unicode(platform.platform()), - 'kallithea_version': kallithea.__version__, - 'git_version': safe_unicode(check_git_version()), - 'git_path': kallithea.CONFIG.get('git_path') - } - return info - - -class Ui(Base, BaseModel): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = ( - UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - - HOOK_UPDATE = 'changegroup.update' - HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'changegroup.push_logger' - HOOK_PRE_PUSH = 'prechangegroup.pre_push' - HOOK_PULL = 'outgoing.pull_logger' - HOOK_PRE_PULL = 'preoutgoing.pre_pull' - - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - # def __init__(self, section='', key='', value=''): - # self.ui_section = section - # self.ui_key = key - # self.ui_value = value - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.ui_key == key).scalar() - - @classmethod - def get_builtin_hooks(cls): - q = cls.query() - q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - return q.all() - - @classmethod - def get_custom_hooks(cls): - q = cls.query() - q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, - cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, - cls.HOOK_PULL, cls.HOOK_PRE_PULL])) - q = q.filter(cls.ui_section == 'hooks') - return q.all() - - @classmethod - def get_repos_location(cls): - return cls.get_by_key('/').ui_value - - @classmethod - def create_or_update_hook(cls, key, val): - new_ui = cls.get_by_key(key) or cls() - new_ui.ui_section = 'hooks' - new_ui.ui_active = True - new_ui.ui_key = key - new_ui.ui_value = val - - Session().add(new_ui) - - def __repr__(self): - return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section, - self.ui_key, self.ui_value) - - -class User(Base, BaseModel): - __tablename__ = 'users' - __table_args__ = ( - UniqueConstraint('username'), UniqueConstraint('email'), - Index('u_username_idx', 'username'), - Index('u_email_idx', 'email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - DEFAULT_USER = 'default' - DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}' - - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) - name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) - extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data - - user_log = relationship('UserLog') - user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relationship('Repository') - user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') - - repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') - repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') - - group_member = relationship('UserGroupMember', cascade='all') - - notifications = relationship('UserNotification', cascade='all') - # notifications assigned to this user - user_created_notifications = relationship('Notification', cascade='all') - # comments created by this user - user_comments = relationship('ChangesetComment', cascade='all') - #extra emails for this user - user_emails = relationship('UserEmailMap', cascade='all') - #extra API keys - user_api_keys = relationship('UserApiKeys', cascade='all') - - - @hybrid_property - def email(self): - return self._email - - @email.setter - def email(self, val): - self._email = val.lower() if val else None - - @property - def firstname(self): - # alias for future - return self.name - - @property - def emails(self): - other = UserEmailMap.query().filter(UserEmailMap.user==self).all() - return [self.email] + [x.email for x in other] - - @property - def api_keys(self): - other = UserApiKeys.query().filter(UserApiKeys.user==self).all() - return [self.api_key] + [x.api_key for x in other] - - @property - def ip_addresses(self): - ret = UserIpMap.query().filter(UserIpMap.user == self).all() - return [x.ip_addr for x in ret] - - @property - def username_and_name(self): - return '%s (%s %s)' % (self.username, self.firstname, self.lastname) - - @property - def full_name(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def full_name_or_username(self): - return ('%s %s' % (self.firstname, self.lastname) - if (self.firstname and self.lastname) else self.username) - - @property - def full_contact(self): - return '%s %s <%s>' % (self.firstname, self.lastname, self.email) - - @property - def short_contact(self): - return '%s %s' % (self.firstname, self.lastname) - - @property - def is_admin(self): - return self.admin - - @property - def AuthUser(self): - """ - Returns instance of AuthUser for this user - """ - from kallithea.lib.auth import AuthUser - return AuthUser(user_id=self.user_id, api_key=self.api_key, - username=self.username) - - @hybrid_property - def user_data(self): - if not self._user_data: - return {} - - try: - return json.loads(self._user_data) - except TypeError: - return {} - - @user_data.setter - def user_data(self, val): - try: - self._user_data = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - - @classmethod - def get_by_username(cls, username, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.username.ilike(username)) - else: - q = cls.query().filter(cls.username == username) - - if cache: - q = q.options(FromCache( - "sql_cache_short", - "get_user_%s" % _hash_key(username) - ) - ) - return q.scalar() - - @classmethod - def get_by_api_key(cls, api_key, cache=False, fallback=True): - q = cls.query().filter(cls.api_key == api_key) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % api_key)) - res = q.scalar() - - if fallback and not res: - #fallback to additional keys - _res = UserApiKeys.query()\ - .filter(UserApiKeys.api_key == api_key)\ - .filter(or_(UserApiKeys.expires == -1, - UserApiKeys.expires >= time.time()))\ - .first() - if _res: - res = _res.user - return res - - @classmethod - def get_by_email(cls, email, case_insensitive=False, cache=False): - if case_insensitive: - q = cls.query().filter(cls.email.ilike(email)) - else: - q = cls.query().filter(cls.email == email) - - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_key_%s" % email)) - - ret = q.scalar() - if ret is None: - q = UserEmailMap.query() - # try fetching in alternate email map - if case_insensitive: - q = q.filter(UserEmailMap.email.ilike(email)) - else: - q = q.filter(UserEmailMap.email == email) - q = q.options(joinedload(UserEmailMap.user)) - if cache: - q = q.options(FromCache("sql_cache_short", - "get_email_map_key_%s" % email)) - ret = getattr(q.scalar(), 'user', None) - - return ret - - @classmethod - def get_from_cs_author(cls, author): - """ - Tries to get User objects out of commit author string - - :param author: - """ - from kallithea.lib.helpers import email, author_name - # Valid email in the attribute passed, see if they're in the system - _email = email(author) - if _email: - user = cls.get_by_email(_email, case_insensitive=True) - if user: - return user - # Maybe we can match by username? - _author = author_name(author) - user = cls.get_by_username(_author, case_insensitive=True) - if user: - return user - - def update_lastlogin(self): - """Update user lastlogin""" - self.last_login = datetime.datetime.now() - Session().add(self) - log.debug('updated user %s lastlogin', self.username) - - @classmethod - def get_first_admin(cls): - user = User.query().filter(User.admin == True).first() - if user is None: - raise Exception('Missing administrative account!') - return user - - @classmethod - def get_default_user(cls, cache=False): - user = User.get_by_username(User.DEFAULT_USER, cache=cache) - if user is None: - raise Exception('Missing default account!') - return user - - def get_api_data(self): - """ - 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, - api_keys=user.api_keys, - active=user.active, - admin=user.admin, - extern_type=user.extern_type, - extern_name=user.extern_name, - last_login=user.last_login, - ip_addresses=user.ip_addresses - ) - return data - - def __json__(self): - data = dict( - full_name=self.full_name, - full_name_or_username=self.full_name_or_username, - short_contact=self.short_contact, - full_contact=self.full_contact - ) - data.update(self.get_api_data()) - return data - - -class UserApiKeys(Base, BaseModel): - __tablename__ = 'user_api_keys' - __table_args__ = ( - Index('uak_api_key_idx', 'api_key'), - Index('uak_api_key_expires_idx', 'api_key', 'expires'), - UniqueConstraint('api_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - user_api_key_id = Column("user_api_key_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) - api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True) - description = Column('description', UnicodeText(1024)) - expires = Column('expires', Float(53), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - user = relationship('User', lazy='joined') - - @property - def expired(self): - if self.expires == -1: - return False - return time.time() > self.expires - - -class UserEmailMap(Base, BaseModel): - __tablename__ = 'user_email_map' - __table_args__ = ( - Index('uem_email_idx', 'email'), - UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - email_id = Column("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), 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 UserIpMap(Base, BaseModel): - __tablename__ = 'user_ip_map' - __table_args__ = ( - UniqueConstraint('user_id', 'ip_addr'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - __mapper_args__ = {} - - ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=True) - user = relationship('User', lazy='joined') - - @classmethod - def _get_ip_range(cls, ip_addr): - from kallithea.lib import ipaddr - net = ipaddr.IPNetwork(address=ip_addr) - return [str(net.network), str(net.broadcast)] - - def __json__(self): - return dict( - ip_addr=self.ip_addr, - ip_range=self._get_ip_range(self.ip_addr) - ) - - def __unicode__(self): - return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__, - self.user_id, self.ip_addr) - -class UserLog(Base, BaseModel): - __tablename__ = 'user_logs' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None) - action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.repository_name, - self.action) - - @property - def action_as_day(self): - return datetime.date(*self.action_date.timetuple()[:3]) - - user = relationship('User') - repository = relationship('Repository', cascade='') - - -class UserGroup(Base, BaseModel): - __tablename__ = 'users_groups' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) - user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, 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) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data - - members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") - users_group_to_perm = relationship('UserGroupToPerm', cascade='all') - users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') - user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') - - user = relationship('User') - - @hybrid_property - def group_data(self): - if not self._group_data: - return {} - - try: - return json.loads(self._group_data) - except TypeError: - return {} - - @group_data.setter - def group_data(self, val): - try: - self._group_data = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, - self.users_group_id, - 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, user_group_id, cache=False): - user_group = cls.query() - if cache: - user_group = user_group.options(FromCache("sql_cache_short", - "get_users_group_%s" % user_group_id)) - return user_group.get(user_group_id) - - def get_api_data(self, with_members=True): - user_group = self - - data = dict( - users_group_id=user_group.users_group_id, - group_name=user_group.users_group_name, - group_description=user_group.user_group_description, - active=user_group.users_group_active, - owner=user_group.user.username, - ) - if with_members: - members = [] - for user in user_group.members: - user = user.user - members.append(user.get_api_data()) - data['members'] = members - - return data - - -class UserGroupMember(Base, BaseModel): - __tablename__ = 'users_groups_members' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - - user = relationship('User', lazy='joined') - users_group = relationship('UserGroup') - - def __init__(self, gr_id='', u_id=''): - self.users_group_id = gr_id - self.user_id = u_id - - -class RepositoryField(Base, BaseModel): - __tablename__ = 'repositories_fields' - __table_args__ = ( - UniqueConstraint('repository_id', 'field_key'), # no-multi field - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields - - repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) - field_key = Column("field_key", String(250, convert_unicode=False)) - field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False) - field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False) - field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False) - field_type = Column("field_type", String(256), nullable=False, unique=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repository = relationship('Repository') - - @property - def field_key_prefixed(self): - return 'ex_%s' % self.field_key - - @classmethod - def un_prefix_key(cls, key): - if key.startswith(cls.PREFIX): - return key[len(cls.PREFIX):] - return key - - @classmethod - def get_by_key_name(cls, key, repo): - row = cls.query()\ - .filter(cls.repository == repo)\ - .filter(cls.field_key == key).scalar() - return row - - -class Repository(Base, BaseModel): - __tablename__ = 'repositories' - __table_args__ = ( - UniqueConstraint('repo_name'), - Index('r_repo_name_idx', 'repo_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}' - DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}' - - STATE_CREATED = 'repo_state_created' - STATE_PENDING = 'repo_state_pending' - STATE_ERROR = 'repo_state_error' - - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) - repo_state = Column("repo_state", String(255), nullable=True) - - clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - private = Column("private", Boolean(), nullable=True, unique=None, default=None) - enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - _landing_revision = Column("landing_revision", String(255, convert_unicode=False), 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), nullable=True, unique=False, default=None) - _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data - - fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) - - user = relationship('User') - fork = relationship('Repository', remote_side=repo_id) - group = relationship('RepoGroup') - repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') - stats = relationship('Statistics', cascade='all', uselist=False) - - followers = relationship('UserFollowing', - primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', - cascade='all') - extra_fields = relationship('RepositoryField', - cascade="all, delete, delete-orphan") - - 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, - safe_unicode(self.repo_name)) - - @hybrid_property - def landing_rev(self): - # always should return [rev_type, rev] - if self._landing_revision: - _rev_info = self._landing_revision.split(':') - if len(_rev_info) < 2: - _rev_info.insert(0, 'rev') - return [_rev_info[0], _rev_info[1]] - return [None, None] - - @landing_rev.setter - def landing_rev(self, val): - if ':' not in val: - raise ValueError('value must be delimited with `:` and consist ' - 'of :, got %s instead' % val) - self._landing_revision = val - - @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 - - @hybrid_property - def changeset_cache(self): - from kallithea.lib.vcs.backends.base import EmptyChangeset - dummy = EmptyChangeset().__json__() - if not self._changeset_cache: - return dummy - try: - return json.loads(self._changeset_cache) - except TypeError: - return dummy - - @changeset_cache.setter - def changeset_cache(self, val): - try: - self._changeset_cache = json.dumps(val) - except Exception: - log.error(traceback.format_exc()) - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def normalize_repo_name(cls, repo_name): - """ - Normalizes os specific repo_name to the format internally stored inside - dabatabase using URL_SEP - - :param cls: - :param repo_name: - """ - return cls.url_sep().join(repo_name.split(os.sep)) - - @classmethod - def get_by_repo_name(cls, repo_name): - q = Session().query(cls).filter(cls.repo_name == repo_name) - q = q.options(joinedload(Repository.fork))\ - .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] - repo_name = cls.normalize_repo_name(repo_name) - return cls.get_by_repo_name(repo_name.strip(URL_SEP)) - - @classmethod - def get_repo_forks(cls, repo_id): - return cls.query().filter(Repository.fork_id == repo_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all repos are stored - - :param cls: - """ - q = Session().query(Ui)\ - .filter(Ui.ui_key == cls.url_sep()) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return q.one().ui_value - - @property - def forks(self): - """ - Return forks of this repo - """ - return Repository.get_repo_forks(self.repo_id) - - @property - def parent(self): - """ - Returns fork parent - """ - return self.fork - - @property - def just_name(self): - return self.repo_name.split(Repository.url_sep())[-1] - - @property - def groups_with_parents(self): - groups = [] - if self.group is None: - return groups - - cur_gr = self.group - groups.insert(0, cur_gr) - while 1: - gr = getattr(cur_gr, 'parent_group', None) - cur_gr = cur_gr.parent_group - if gr is None: - break - groups.insert(0, gr) - - return groups - - @property - def groups_and_repo(self): - return self.groups_with_parents, self.just_name, self.repo_name - - @LazyProperty - def repo_path(self): - """ - Returns base full path for that repository means where it actually - exists on a filesystem - """ - q = Session().query(Ui).filter(Ui.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(*map(safe_unicode, p)) - - @property - def cache_keys(self): - """ - Returns associated cache keys for that repo - """ - return CacheInvalidation.query()\ - .filter(CacheInvalidation.cache_args == self.repo_name)\ - .order_by(CacheInvalidation.cache_key)\ - .all() - - def get_new_name(self, repo_name): - """ - returns new full repository name based on assigned group and new new - - :param group_name: - """ - path_prefix = self.group.full_path_splitted if self.group else [] - return Repository.url_sep().join(path_prefix + [repo_name]) - - @property - def _ui(self): - """ - Creates an db based ui object for this repository - """ - from kallithea.lib.utils import make_ui - return make_ui('db', clear_session=False) - - @classmethod - def is_valid(cls, repo_name): - """ - returns True if given repo name is a valid filesystem repository - - :param cls: - :param repo_name: - """ - from kallithea.lib.utils import is_valid_repo - - return is_valid_repo(repo_name, cls.base_path()) - - def get_api_data(self): - """ - Common function for generating repo api data - - """ - repo = self - data = dict( - repo_id=repo.repo_id, - repo_name=repo.repo_name, - repo_type=repo.repo_type, - clone_uri=repo.clone_uri, - private=repo.private, - created_on=repo.created_on, - description=repo.description, - landing_rev=repo.landing_rev, - owner=repo.user.username, - fork_of=repo.fork.repo_name if repo.fork else None, - enable_statistics=repo.enable_statistics, - enable_locking=repo.enable_locking, - enable_downloads=repo.enable_downloads, - last_changeset=repo.changeset_cache, - locked_by=User.get(self.locked[0]).get_api_data() \ - if self.locked[0] else None, - locked_date=time_to_datetime(self.locked[1]) \ - if self.locked[1] else None - ) - rc_config = Setting.get_app_settings() - repository_fields = str2bool(rc_config.get('repository_fields')) - if repository_fields: - for f in self.extra_fields: - data[f.field_key_prefixed] = f.field_value - - return data - - @classmethod - def lock(cls, repo, user_id, lock_time=None): - if not lock_time: - lock_time = time.time() - repo.locked = [user_id, lock_time] - Session().add(repo) - Session().commit() - - @classmethod - def unlock(cls, repo): - repo.locked = None - Session().add(repo) - Session().commit() - - @classmethod - def getlock(cls, repo): - return repo.locked - - @property - def last_db_change(self): - return self.updated_on - - def clone_url(self, **override): - import kallithea.lib.helpers as h - qualified_home_url = h.canonical_url('home') - - uri_tmpl = None - if 'with_id' in override: - uri_tmpl = self.DEFAULT_CLONE_URI_ID - del override['with_id'] - - if 'uri_tmpl' in override: - uri_tmpl = override['uri_tmpl'] - del override['uri_tmpl'] - - # we didn't override our tmpl from **overrides - if not uri_tmpl: - uri_tmpl = self.DEFAULT_CLONE_URI - try: - from pylons import tmpl_context as c - uri_tmpl = c.clone_uri_tmpl - except Exception: - # in any case if we call this outside of request context, - # ie, not having tmpl_context set up - pass - - return get_clone_url(uri_tmpl=uri_tmpl, - qualified_home_url=qualified_home_url, - repo_name=self.repo_name, - repo_id=self.repo_id, **override) - - def set_state(self, state): - self.repo_state = state - Session().add(self) - #========================================================================== - # SCM PROPERTIES - #========================================================================== - - def get_changeset(self, rev=None): - return get_changeset_safe(self.scm_instance, rev) - - def get_landing_changeset(self): - """ - Returns landing changeset, or if that doesn't exist returns the tip - """ - _rev_type, _rev = self.landing_rev - cs = self.get_changeset(_rev) - if isinstance(cs, EmptyChangeset): - return self.get_changeset() - return cs - - def update_changeset_cache(self, cs_cache=None): - """ - Update cache of last changeset for repository, keys should be:: - - short_id - raw_id - revision - message - date - author - - :param cs_cache: - """ - from kallithea.lib.vcs.backends.base import BaseChangeset - if cs_cache is None: - cs_cache = EmptyChangeset() - # use no-cache version here - scm_repo = self.scm_instance_no_cache() - if scm_repo: - cs_cache = scm_repo.get_changeset() - - if isinstance(cs_cache, BaseChangeset): - cs_cache = cs_cache.__json__() - - if (cs_cache != self.changeset_cache or not self.changeset_cache): - _default = datetime.datetime.fromtimestamp(0) - last_change = cs_cache.get('date') or _default - log.debug('updated repo %s with new cs cache %s', - self.repo_name, cs_cache) - self.updated_on = last_change - self.changeset_cache = cs_cache - Session().add(self) - Session().commit() - else: - log.debug('Skipping repo:%s already with latest changes', - self.repo_name) - - @property - def tip(self): - return self.get_changeset('tip') - - @property - def author(self): - return self.tip.author - - @property - def last_change(self): - return self.scm_instance.last_change - - def get_comments(self, revisions=None): - """ - Returns comments for this repository grouped by revisions - - :param revisions: filter query by revisions only - """ - cmts = ChangesetComment.query()\ - .filter(ChangesetComment.repo == self) - if revisions: - cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) - grouped = collections.defaultdict(list) - for cmt in cmts.all(): - grouped[cmt.revision].append(cmt) - return grouped - - def statuses(self, revisions=None): - """ - Returns statuses for this repository - - :param revisions: list of revisions to get statuses for - """ - - 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 - - def _repo_size(self): - from kallithea.lib import helpers as h - log.debug('calculating repository size...') - return h.format_byte_size(self.scm_instance.size) - - #========================================================================== - # SCM CACHE INSTANCE - #========================================================================== - - def set_invalidate(self): - """ - Mark caches of this repo as invalid. - """ - CacheInvalidation.set_invalidate(self.repo_name) - - def scm_instance_no_cache(self): - return self.__get_instance() - - @property - def scm_instance(self): - import kallithea - full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache')) - if full_cache: - return self.scm_instance_cached() - return self.__get_instance() - - def scm_instance_cached(self, valid_cache_keys=None): - @cache_region('long_term') - def _c(repo_name): - return self.__get_instance() - rn = self.repo_name - - valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) - if not valid: - log.debug('Cache for %s invalidated, getting new object', rn) - region_invalidate(_c, None, rn) - else: - log.debug('Getting obj for %s from cache', rn) - 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 from %s', - alias, repo_full_path) - 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) - else: - repo = backend(repo_full_path, create=False) - - return repo - - def __json__(self): - return dict(landing_rev = self.landing_rev) - -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', 'sqlite_autoincrement': True}, - ) - __mapper_args__ = {'order_by': 'group_name'} - - SEP = ' » ' - - group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None) - group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) - group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None) - enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') - users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') - parent_group = relationship('RepoGroup', remote_side=group_id) - user = relationship('User') - - def __init__(self, group_name='', parent_group=None): - self.group_name = group_name - self.parent_group = parent_group - - def __unicode__(self): - return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, - self.group_name) - - @classmethod - def _generate_choice(cls, repo_group): - from webhelpers.html import literal as _literal - _name = lambda k: _literal(cls.SEP.join(k)) - return repo_group.group_id, _name(repo_group.full_path_splitted) - - @classmethod - def groups_choices(cls, groups=None, show_empty_group=True): - if not groups: - groups = cls.query().all() - - repo_groups = [] - if show_empty_group: - repo_groups = [('-1', u'-- %s --' % _('top level'))] - - repo_groups.extend([cls._generate_choice(x) for x in groups]) - - repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0]) - return repo_groups - - @classmethod - def url_sep(cls): - return URL_SEP - - @classmethod - def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): - if case_insensitive: - gr = cls.query()\ - .filter(cls.group_name.ilike(group_name)) - else: - gr = cls.query()\ - .filter(cls.group_name == group_name) - if cache: - gr = gr.options(FromCache( - "sql_cache_short", - "get_group_%s" % _hash_key(group_name) - ) - ) - return gr.scalar() - - @property - def parents(self): - parents_recursion_limit = 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 infinite loops - log.error('group nested more than %s', - parents_recursion_limit) - break - - groups.insert(0, gr) - return groups - - @property - def children(self): - return RepoGroup.query().filter(RepoGroup.parent_group == self) - - @property - def name(self): - return self.group_name.split(RepoGroup.url_sep())[-1] - - @property - def full_path(self): - return self.group_name - - @property - def full_path_splitted(self): - return self.group_name.split(RepoGroup.url_sep()) - - @property - def repositories(self): - return Repository.query()\ - .filter(Repository.group == self)\ - .order_by(Repository.repo_name) - - @property - def repositories_recursive_count(self): - cnt = self.repositories.count() - - def children_count(group): - cnt = 0 - for child in group.children: - cnt += child.repositories.count() - cnt += children_count(child) - return cnt - - return cnt + children_count(self) - - def _recursive_objects(self, include_repos=True): - all_ = [] - - def _get_members(root_gr): - if include_repos: - for r in root_gr.repositories: - all_.append(r) - childs = root_gr.children.all() - if childs: - for gr in childs: - all_.append(gr) - _get_members(gr) - - _get_members(self) - return [self] + all_ - - def recursive_groups_and_repos(self): - """ - Recursive return all groups, with repositories in those groups - """ - return self._recursive_objects() - - def recursive_groups(self): - """ - Returns all children groups for this group including children of children - """ - return self._recursive_objects(include_repos=False) - - def get_new_name(self, group_name): - """ - returns new full group name based on parent and new name - - :param group_name: - """ - path_prefix = (self.parent_group.full_path_splitted if - self.parent_group else []) - return RepoGroup.url_sep().join(path_prefix + [group_name]) - - def get_api_data(self): - """ - Common function for generating api data - - """ - group = self - data = dict( - group_id=group.group_id, - group_name=group.group_name, - group_description=group.group_description, - parent_group=group.parent_group.group_name if group.parent_group else None, - repositories=[x.repo_name for x in group.repositories], - owner=group.user.username - ) - return data - - -class Permission(Base, BaseModel): - __tablename__ = 'permissions' - __table_args__ = ( - Index('p_perm_name_idx', 'permission_name'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - PERMS = [ - ('hg.admin', _('Kallithea Administrator')), - - ('repository.none', _('Repository no access')), - ('repository.read', _('Repository read access')), - ('repository.write', _('Repository write access')), - ('repository.admin', _('Repository admin access')), - - ('group.none', _('Repository group no access')), - ('group.read', _('Repository group read access')), - ('group.write', _('Repository group write access')), - ('group.admin', _('Repository group admin access')), - - ('usergroup.none', _('User group no access')), - ('usergroup.read', _('User group read access')), - ('usergroup.write', _('User group write access')), - ('usergroup.admin', _('User group admin access')), - - ('hg.repogroup.create.false', _('Repository Group creation disabled')), - ('hg.repogroup.create.true', _('Repository Group creation enabled')), - - ('hg.usergroup.create.false', _('User Group creation disabled')), - ('hg.usergroup.create.true', _('User Group creation enabled')), - - ('hg.create.none', _('Repository creation disabled')), - ('hg.create.repository', _('Repository creation enabled')), - ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')), - ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')), - - ('hg.fork.none', _('Repository forking disabled')), - ('hg.fork.repository', _('Repository forking enabled')), - - ('hg.register.none', _('Registration disabled')), - ('hg.register.manual_activate', _('User Registration with manual account activation')), - ('hg.register.auto_activate', _('User Registration with automatic account activation')), - - ('hg.extern_activate.manual', _('Manual activation of external account')), - ('hg.extern_activate.auto', _('Automatic activation of external account')), - - ] - - #definition of system default permissions for DEFAULT user - DEFAULT_USER_PERMISSIONS = [ - 'repository.read', - 'group.read', - 'usergroup.read', - 'hg.create.repository', - 'hg.create.write_on_repogroup.true', - 'hg.fork.repository', - 'hg.register.manual_activate', - 'hg.extern_activate.auto', - ] - - # defines which permissions are more important higher the more important - # Weight defines which permissions are more important. - # The higher number the more important. - PERM_WEIGHTS = { - 'repository.none': 0, - 'repository.read': 1, - 'repository.write': 3, - 'repository.admin': 4, - - 'group.none': 0, - 'group.read': 1, - 'group.write': 3, - 'group.admin': 4, - - 'usergroup.none': 0, - 'usergroup.read': 1, - 'usergroup.write': 3, - 'usergroup.admin': 4, - 'hg.repogroup.create.false': 0, - 'hg.repogroup.create.true': 1, - - 'hg.usergroup.create.false': 0, - 'hg.usergroup.create.true': 1, - - 'hg.fork.none': 0, - 'hg.fork.repository': 1, - 'hg.create.none': 0, - 'hg.create.repository': 1 - } - - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - - def __unicode__(self): - return u"<%s('%s:%s')>" % ( - self.__class__.__name__, self.permission_id, self.permission_name - ) - - @classmethod - def get_by_key(cls, key): - return cls.query().filter(cls.permission_name == key).scalar() - - @classmethod - def get_default_perms(cls, default_user_id): - q = Session().query(UserRepoToPerm, Repository, cls)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_group_perms(cls, default_user_id): - q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ - .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ - .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserRepoGroupToPerm.user_id == default_user_id) - - return q.all() - - @classmethod - def get_default_user_group_perms(cls, default_user_id): - q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ - .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ - .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ - .filter(UserUserGroupToPerm.user_id == default_user_id) - - return q.all() - - -class UserRepoToPerm(Base, BaseModel): - __tablename__ = 'repo_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - repo_to_perm_id = Column("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 => %s >' % (self.user, self.repository) - - -class UserUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'user_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_user_group_to_perm_id = Column("user_user_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) - permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - user = relationship('User') - user_group = relationship('UserGroup') - permission = relationship('Permission') - - @classmethod - def create(cls, user, user_group, permission): - n = cls() - n.user = user - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.user_group) - - -class UserToPerm(Base, BaseModel): - __tablename__ = 'user_to_perm' - __table_args__ = ( - UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_to_perm_id = Column("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') - - def __unicode__(self): - return u'<%s => %s >' % (self.user, self.permission) - - -class UserGroupRepoToPerm(Base, BaseModel): - __tablename__ = 'users_group_repo_to_perm' - __table_args__ = ( - UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - users_group_to_perm_id = Column("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('UserGroup') - permission = relationship('Permission') - repository = relationship('Repository') - - @classmethod - def create(cls, users_group, repository, permission): - n = cls() - n.users_group = users_group - n.repository = repository - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.users_group, self.repository) - - -class UserGroupUserGroupToPerm(Base, BaseModel): - __tablename__ = 'user_group_user_group_to_perm' - __table_args__ = ( - UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), - CheckConstraint('target_user_group_id != user_group_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - target_user_group_id = Column("target_user_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) - user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) - - target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') - user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') - permission = relationship('Permission') - - @classmethod - def create(cls, target_user_group, user_group, permission): - n = cls() - n.target_user_group = target_user_group - n.user_group = user_group - n.permission = permission - Session().add(n) - return n - - def __unicode__(self): - return u' %s >' % (self.target_user_group, self.user_group) - - -class UserGroupToPerm(Base, BaseModel): - __tablename__ = 'users_group_to_perm' - __table_args__ = ( - UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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('UserGroup') - 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', 'sqlite_autoincrement': True} - ) - - 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') - - @classmethod - def create(cls, user, repository_group, permission): - n = cls() - n.user = user - n.group = repository_group - n.permission = permission - Session().add(n) - return n - - -class UserGroupRepoGroupToPerm(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', 'sqlite_autoincrement': True} - ) - - 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('UserGroup') - permission = relationship('Permission') - group = relationship('RepoGroup') - - @classmethod - def create(cls, user_group, repository_group, permission): - n = cls() - n.users_group = user_group - n.group = repository_group - n.permission = permission - Session().add(n) - return n - - -class Statistics(Base, BaseModel): - __tablename__ = 'statistics' - __table_args__ = ( - UniqueConstraint('repository_id'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True} - ) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) - follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) - - user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relationship('Repository', order_by='Repository.repo_name') - - @classmethod - def get_repo_followers(cls, repo_id): - return cls.query().filter(cls.follows_repo_id == repo_id) - - -class CacheInvalidation(Base, BaseModel): - __tablename__ = 'cache_invalidation' - __table_args__ = ( - UniqueConstraint('cache_key'), - Index('key_idx', 'cache_key'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - # cache_id, not used - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - # cache_key as created by _get_cache_key - cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - # cache_args is a repo_name - cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None) - # instance sets cache_active True when it is caching, - # other instances set cache_active to False to indicate that this cache is invalid - cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) - - def __init__(self, cache_key, repo_name=''): - self.cache_key = cache_key - self.cache_args = repo_name - self.cache_active = False - - def __unicode__(self): - return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, - self.cache_id, self.cache_key, self.cache_active) - - def _cache_key_partition(self): - prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) - return prefix, repo_name, suffix - - def get_prefix(self): - """ - get prefix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[0] - - def get_suffix(self): - """ - get suffix that might have been used in _get_cache_key to - generate self.cache_key. Only used for informational purposes - in repo_edit.html. - """ - # prefix, repo_name, suffix - return self._cache_key_partition()[2] - - @classmethod - def clear_cache(cls): - """ - Delete all cache keys from database. - Should only be run when all instances are down and all entries thus stale. - """ - cls.query().delete() - Session().commit() - - @classmethod - def _get_cache_key(cls, key): - """ - Wrapper for generating a unique cache key for this instance and "key". - key must / will start with a repo_name which will be stored in .cache_args . - """ - import kallithea - prefix = kallithea.CONFIG.get('instance_id', '') - return "%s%s" % (prefix, key) - - @classmethod - def set_invalidate(cls, repo_name, delete=False): - """ - Mark all caches of a repo as invalid in the database. - """ - inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() - log.debug('for repo %s got %s invalidation objects', - safe_str(repo_name), inv_objs) - try: - for inv_obj in inv_objs: - log.debug('marking %s key for invalidation based on repo_name=%s', - inv_obj, safe_str(repo_name)) - if delete: - Session().delete(inv_obj) - else: - inv_obj.cache_active = False - Session().add(inv_obj) - Session().commit() - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - - @classmethod - def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): - """ - Mark this cache key as active and currently cached. - Return True if the existing cache registration still was valid. - Return False to indicate that it had been invalidated and caches should be refreshed. - """ - - key = (repo_name + '_' + kind) if kind else repo_name - cache_key = cls._get_cache_key(key) - - if valid_cache_keys and cache_key in valid_cache_keys: - return True - - try: - inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() - if not inv_obj: - inv_obj = CacheInvalidation(cache_key, repo_name) - was_valid = inv_obj.cache_active - inv_obj.cache_active = True - Session().add(inv_obj) - Session().commit() - return was_valid - except Exception: - log.error(traceback.format_exc()) - Session().rollback() - return False - - @classmethod - def get_valid_cache_keys(cls): - """ - Return opaque object with information of which caches still are valid - and can be used without checking for invalidation. - """ - return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) - - -class ChangesetComment(Base, BaseModel): - __tablename__ = 'changeset_comments' - __table_args__ = ( - Index('cc_revision_idx', 'revision'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) - repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) - revision = Column('revision', String(40), nullable=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) - line_no = Column('line_no', Unicode(10), nullable=True) - hl_lines = Column('hl_lines', Unicode(512), nullable=True) - f_path = Column('f_path', Unicode(1000), nullable=True) - user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) - text = Column('text', UnicodeText(25000), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - author = relationship('User', lazy='joined') - repo = relationship('Repository') - status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") - pull_request = relationship('PullRequest', lazy='joined') - - @classmethod - def get_users(cls, revision=None, pull_request_id=None): - """ - Returns user associated with this ChangesetComment. ie those - who actually commented - - :param cls: - :param revision: - """ - q = Session().query(User)\ - .join(ChangesetComment.author) - if revision: - q = q.filter(cls.revision == revision) - elif pull_request_id: - q = q.filter(cls.pull_request_id == pull_request_id) - return q.all() - - -class ChangesetStatus(Base, BaseModel): - __tablename__ = 'changeset_statuses' - __table_args__ = ( - Index('cs_revision_idx', 'revision'), - Index('cs_version_idx', 'version'), - UniqueConstraint('repo_id', 'revision', 'version'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - 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', 'sqlite_autoincrement': True}, - ) - - # values for .status - 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) # only for closedness, not approve/reject/etc - 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) - - @property - def org_ref_parts(self): - return self.org_ref.split(':') - - @property - def other_ref_parts(self): - return self.other_ref.split(':') - - author = relationship('User', lazy='joined') - reviewers = relationship('PullRequestReviewers', - cascade="all, delete, delete-orphan") - 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 - - @property - def last_review_status(self): - return self.statuses[-1].status if self.statuses else '' - - 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', 'sqlite_autoincrement': True}, - ) - - def __init__(self, user=None, pull_request=None): - self.user = user - self.pull_request = pull_request - - pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) - pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) - - user = relationship('User') - pull_request = relationship('PullRequest') - - -class Notification(Base, BaseModel): - __tablename__ = 'notifications' - __table_args__ = ( - Index('notification_type_idx', 'type'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - - TYPE_CHANGESET_COMMENT = u'cs_comment' - TYPE_MESSAGE = u'message' - TYPE_MENTION = u'mention' - TYPE_REGISTRATION = u'registration' - TYPE_PULL_REQUEST = u'pull_request' - TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' - - notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) - subject = Column('subject', Unicode(512), nullable=True) - body = Column('body', UnicodeText(50000), nullable=True) - created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - type_ = Column('type', Unicode(256)) - - created_by_user = relationship('User') - notifications_to_users = relationship('UserNotification', lazy='joined', - cascade="all, delete, delete-orphan") - - @property - def recipients(self): - return [x.user for x in UserNotification.query()\ - .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user_id.asc()).all()] - - @classmethod - def create(cls, created_by, subject, body, recipients, type_=None): - if type_ is None: - type_ = Notification.TYPE_MESSAGE - - notification = cls() - notification.created_by_user = created_by - notification.subject = subject - notification.body = body - notification.type_ = type_ - notification.created_on = datetime.datetime.now() - - for u in recipients: - assoc = UserNotification() - assoc.notification = notification - u.notifications.append(assoc) - Session().add(notification) - return notification - - @property - def description(self): - from kallithea.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', 'sqlite_autoincrement': True} - ) - 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 Gist(Base, BaseModel): - __tablename__ = 'gists' - __table_args__ = ( - Index('g_gist_access_id_idx', 'gist_access_id'), - Index('g_created_on_idx', 'created_on'), - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} - ) - GIST_PUBLIC = u'public' - GIST_PRIVATE = u'private' - DEFAULT_FILENAME = u'gistfile1.txt' - - gist_id = Column('gist_id', Integer(), primary_key=True) - gist_access_id = Column('gist_access_id', Unicode(250)) - gist_description = Column('gist_description', UnicodeText(1024)) - gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) - gist_expires = Column('gist_expires', Float(53), nullable=False) - gist_type = Column('gist_type', Unicode(128), nullable=False) - created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) - - owner = relationship('User') - - def __repr__(self): - return '' % (self.gist_type, self.gist_access_id) - - @classmethod - def get_or_404(cls, id_): - res = cls.query().filter(cls.gist_access_id == id_).scalar() - if not res: - raise HTTPNotFound - return res - - @classmethod - def get_by_access_id(cls, gist_access_id): - return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() - - def gist_url(self): - import kallithea - alias_url = kallithea.CONFIG.get('gist_alias_url') - if alias_url: - return alias_url.replace('{gistid}', self.gist_access_id) - - import kallithea.lib.helpers as h - return h.canonical_url('gist', gist_id=self.gist_access_id) - - @classmethod - def base_path(cls): - """ - Returns base path when all gists are stored - - :param cls: - """ - from kallithea.model.gist import GIST_STORE_LOC - q = Session().query(Ui)\ - .filter(Ui.ui_key == URL_SEP) - q = q.options(FromCache("sql_cache_short", "repository_repo_path")) - return os.path.join(q.one().ui_value, GIST_STORE_LOC) - - def get_api_data(self): - """ - Common function for generating gist related data for API - """ - gist = self - data = dict( - gist_id=gist.gist_id, - type=gist.gist_type, - access_id=gist.gist_access_id, - description=gist.gist_description, - url=gist.gist_url(), - expires=gist.gist_expires, - created_on=gist.created_on, - ) - return data - - def __json__(self): - data = dict( - ) - data.update(self.get_api_data()) - return data - ## SCM functions - - @property - def scm_instance(self): - from kallithea.lib.vcs import get_repo - base_path = self.base_path() - return get_repo(os.path.join(*map(safe_str, - [base_path, self.gist_access_id]))) - - -class DbMigrateVersion(Base, BaseModel): - __tablename__ = 'db_migrate_version' - __table_args__ = ( - {'extend_existing': True, 'mysql_engine': 'InnoDB', - 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, - ) - repository_id = Column('repository_id', String(250), primary_key=True) - repository_path = Column('repository_path', Text) - version = Column('version', Integer) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/001_initial_release.py --- a/kallithea/lib/dbmigrate/versions/001_initial_release.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,207 +0,0 @@ -#============================================================================== -# DB INITIAL MODEL -#============================================================================== -import logging -import datetime - -from sqlalchemy import * -from sqlalchemy.exc import DatabaseError -from sqlalchemy.orm import relation -from sqlalchemy.orm.session import Session -from kallithea.model.meta import Base - -from kallithea.lib.dbmigrate.migrate import * - -from kallithea import DB_PREFIX - -log = logging.getLogger(__name__) - -class Setting(Base): - __tablename__ = DB_PREFIX + 'settings' - __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True}) - app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - app_settings_value = Column("app_settings_value", String(length=None, 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 - - def __repr__(self): - return "" % (self.app_settings_name, - self.app_settings_value) - -class Ui(Base): - __tablename__ = DB_PREFIX + 'ui' - __table_args__ = {'useexisting':True} - ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) - - -class User(Base): - __tablename__ = 'users' - __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True}) - user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(length=None, 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=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - email = Column("email", String(length=None, 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) - is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False) - - user_log = relation('UserLog', cascade='all') - user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') - - repositories = relation('Repository') - user_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') - - @property - def full_contact(self): - return '%s %s <%s>' % (self.name, self.lastname, self.email) - - def __repr__(self): - return "" % (self.user_id, self.username) - - def update_lastlogin(self): - """Update user lastlogin""" - - try: - session = Session.object_session(self) - self.last_login = datetime.datetime.now() - session.add(self) - session.commit() - log.debug('updated user %s lastlogin', self.username) - except (DatabaseError,): - session.rollback() - - -class UserLog(Base): - __tablename__ = 'user_logs' - __table_args__ = {'useexisting':True} - user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) - repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", String(length=None, 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) - - user = relation('User') - repository = relation('Repository') - -class Repository(Base): - __tablename__ = 'repositories' - __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},) - repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) - user_id = Column("user_id", Integer(), ForeignKey(u'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) - description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - fork_id = Column("fork_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None) - - user = relation('User') - fork = relation('Repository', remote_side=repo_id) - repo_to_perm = relation('UserRepoToPerm', cascade='all') - stats = relation('Statistics', cascade='all', uselist=False) - - repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all') - - - def __repr__(self): - return "" % (self.repo_id, self.repo_name) - -class Permission(Base): - __tablename__ = 'permissions' - __table_args__ = {'useexisting':True} - permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - - def __repr__(self): - return "" % (self.permission_id, self.permission_name) - -class UserRepoToPerm(Base): - __tablename__ = 'repo_to_perm' - __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True}) - 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(u'users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) - repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) - - user = relation('User') - permission = relation('Permission') - repository = relation('Repository') - -class UserToPerm(Base): - __tablename__ = 'user_to_perm' - __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True}) - 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(u'users.user_id'), nullable=False, unique=None, default=None) - permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) - - user = relation('User') - permission = relation('Permission') - -class Statistics(Base): - __tablename__ = 'statistics' - __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True}) - stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - repository_id = Column("repository_id", Integer(), ForeignKey(u'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(), nullable=False)#JSON data - commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data - languages = Column("languages", LargeBinary(), nullable=False)#JSON data - - repository = relation('Repository', single_parent=True) - -class UserFollowing(Base): - __tablename__ = 'user_followings' - __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'), - UniqueConstraint('user_id', 'follows_user_id') - , {'useexisting':True}) - - user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) - follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None) - follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None) - - user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id') - - follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') - follows_repository = relation('Repository') - - -class CacheInvalidation(Base): - __tablename__ = 'cache_invalidation' - __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True}) - cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_args = Column("cache_args", String(length=None, 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 __repr__(self): - return "" % (self.cache_id, self.cache_key) - - -def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; bind migrate_engine - # to your metadata - Base.metadata.create_all(bind=migrate_engine, checkfirst=False) - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - Base.metadata.drop_all(bind=migrate_engine, checkfirst=False) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/002_version_1_1_0.py --- a/kallithea/lib/dbmigrate/versions/002_version_1_1_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - - #========================================================================== - # Upgrade of `users` table - #========================================================================== - tblname = 'users' - tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True, - autoload_with=migrate_engine) - - #ADD is_ldap column - is_ldap = Column("is_ldap", Boolean(), nullable=True, - unique=None, default=False) - is_ldap.create(tbl, populate_default=True) - is_ldap.alter(nullable=False) - - #========================================================================== - # Upgrade of `user_logs` table - #========================================================================== - - tblname = 'users' - tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True, - autoload_with=migrate_engine) - - #ADD revision column - revision = Column('revision', TEXT(length=None, convert_unicode=False, - assert_unicode=None), - nullable=True, unique=None, default=None) - revision.create(tbl) - - #========================================================================== - # Upgrade of `repositories` table - #========================================================================== - tblname = 'repositories' - tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True, - autoload_with=migrate_engine) - - #ADD repo_type column# - repo_type = Column("repo_type", String(length=None, convert_unicode=False, - assert_unicode=None), - nullable=True, unique=False, default='hg') - - repo_type.create(tbl, populate_default=True) - #repo_type.alter(nullable=False) - - #ADD statistics column# - enable_statistics = Column("statistics", Boolean(), nullable=True, - unique=None, default=True) - enable_statistics.create(tbl) - - #========================================================================== - # Add table `user_followings` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_1_0 import UserFollowing - UserFollowing().__table__.create() - - #========================================================================== - # Add table `cache_invalidation` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_1_0 import CacheInvalidation - CacheInvalidation().__table__.create() - - return - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/003_version_1_2_0.py --- a/kallithea/lib/dbmigrate/versions/003_version_1_2_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -import logging -import datetime - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - - #========================================================================== - # Add table `groups`` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import Group as Group - Group().__table__.create() - - #========================================================================== - # Add table `group_to_perm` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm - UserRepoGroupToPerm().__table__.create() - - #========================================================================== - # Add table `users_groups` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroup - UserGroup().__table__.create() - - #========================================================================== - # Add table `users_groups_members` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroupMember - UserGroupMember().__table__.create() - - #========================================================================== - # Add table `users_group_repo_to_perm` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroupRepoToPerm - UserGroupRepoToPerm().__table__.create() - - #========================================================================== - # Add table `users_group_to_perm` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroupToPerm - UserGroupToPerm().__table__.create() - - #========================================================================== - # Upgrade of `users` table - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import User - - #add column - ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ldap_dn.create(User().__table__) - - api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key.create(User().__table__) - - #remove old column - is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False) - is_ldap.drop(User().__table__) - - #========================================================================== - # Upgrade of `repositories` table - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_2_0 import Repository - - #ADD clone_uri column# - - clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, - assert_unicode=None), - nullable=True, unique=False, default=None) - - clone_uri.create(Repository().__table__) - - #ADD downloads column# - enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) - enable_downloads.create(Repository().__table__) - - #ADD column created_on - created_on = Column('created_on', DateTime(timezone=False), nullable=True, - unique=None, default=datetime.datetime.now) - created_on.create(Repository().__table__) - - #ADD group_id column# - group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), - nullable=True, unique=False, default=None) - - group_id.create(Repository().__table__) - - #========================================================================== - # Upgrade of `user_followings` table - #========================================================================== - - from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserFollowing - - follows_from = Column('follows_from', DateTime(timezone=False), - nullable=True, unique=None, - default=datetime.datetime.now) - follows_from.create(UserFollowing().__table__) - - return - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/004_version_1_3_0.py --- a/kallithea/lib/dbmigrate/versions/004_version_1_3_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - #========================================================================== - # Add table `users_group_repo_group_to_perm` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserGroupRepoGroupToPerm - UserGroupRepoGroupToPerm().__table__.create() - - #========================================================================== - # Add table `changeset_comments` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import ChangesetComment - ChangesetComment().__table__.create() - - #========================================================================== - # Add table `notifications` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import Notification - Notification().__table__.create() - - #========================================================================== - # Add table `user_to_notification` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserNotification - UserNotification().__table__.create() - - #========================================================================== - # Add unique to table `users_group_to_perm` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserGroupToPerm - tbl = UserGroupToPerm().__table__ - cons = UniqueConstraint('users_group_id', 'permission_id', table=tbl) - cons.create() - - #========================================================================== - # Fix unique constrain on table `user_logs` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserLog - tbl = UserLog().__table__ - col = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), - nullable=False, unique=None, default=None) - col.alter(nullable=True, table=tbl) - - #========================================================================== - # Rename table `group_to_perm` to `user_repo_group_to_perm` - #========================================================================== - tbl = Table('group_to_perm', MetaData(bind=migrate_engine), autoload=True, - autoload_with=migrate_engine) - tbl.rename('user_repo_group_to_perm') - - return - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/005_version_1_3_0.py --- a/kallithea/lib/dbmigrate/versions/005_version_1_3_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - - #========================================================================== - # Change unique constraints of table `repo_to_perm` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserRepoToPerm - tbl = UserRepoToPerm().__table__ - new_cons = UniqueConstraint('user_id', 'repository_id', 'permission_id', table=tbl) - new_cons.create() - old_cons = None - if migrate_engine.name in ['mysql']: - old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl, name="user_id") - elif migrate_engine.name in ['postgresql']: - old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl) - else: - # sqlite doesn't support dropping constraints... - print """Please manually drop UniqueConstraint('user_id', 'repository_id')""" - - if old_cons: - try: - old_cons.drop() - except Exception as e: - # we don't care if this fails really... better to pass migration than - # leave this in intermidiate state - print 'Failed to remove Unique for user_id, repository_id reason %s' % e - - - #========================================================================== - # fix uniques of table `user_repo_group_to_perm` - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserRepoGroupToPerm - tbl = UserRepoGroupToPerm().__table__ - new_cons = UniqueConstraint('group_id', 'permission_id', 'user_id', table=tbl) - new_cons.create() - old_cons = None - - # fix uniqueConstraints - if migrate_engine.name in ['mysql']: - #mysql is givinig troubles here... - old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name="group_id") - elif migrate_engine.name in ['postgresql']: - old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name='group_to_perm_group_id_permission_id_key') - else: - # sqlite doesn't support dropping constraints... - print """Please manually drop UniqueConstraint('group_id', 'permission_id')""" - - if old_cons: - try: - old_cons.drop() - except Exception as e: - # we don't care if this fails really... better to pass migration than - # leave this in intermidiate state - print 'Failed to remove Unique for user_id, repository_id reason %s' % e - - return - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/006_version_1_4_0.py --- a/kallithea/lib/dbmigrate/versions/006_version_1_4_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,171 +0,0 @@ -import logging -import datetime - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - - #========================================================================== - # USEREMAILMAP - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_4_0 import UserEmailMap - tbl = UserEmailMap.__table__ - tbl.create() - #========================================================================== - # PULL REQUEST - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_4_0 import PullRequest - tbl = PullRequest.__table__ - tbl.create() - - #========================================================================== - # PULL REQUEST REVIEWERS - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_4_0 import PullRequestReviewers - tbl = PullRequestReviewers.__table__ - tbl.create() - - #========================================================================== - # CHANGESET STATUS - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_4_0 import ChangesetStatus - tbl = ChangesetStatus.__table__ - tbl.create() - - _reset_base(migrate_engine) - - #========================================================================== - # USERS TABLE - #========================================================================== - from kallithea.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 kallithea.lib.dbmigrate.schema.db_1_3_0 import UserGroup - tbl = UserGroup.__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 kallithea.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 kallithea.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 kallithea.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 kallithea.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 kallithea.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_base(migrate_engine) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/007_version_1_4_0.py --- a/kallithea/lib/dbmigrate/versions/007_version_1_4_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - - #========================================================================== - # CHANGESET_COMMENTS - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_4_0 import ChangesetComment - tbl_name = ChangesetComment.__tablename__ - tbl = Table(tbl_name, - MetaData(bind=migrate_engine), autoload=True, - autoload_with=migrate_engine) - col = tbl.columns.revision - - # remove nullability from revision field - col.alter(nullable=True) - - #========================================================================== - # REPOSITORY - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_4_0 import Repository - tbl = Repository.__table__ - updated_on = Column('updated_on', DateTime(timezone=False), - nullable=True, unique=None) - # create created on column for future lightweight main page - updated_on.create(table=tbl) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/008_version_1_5_0.py --- a/kallithea/lib/dbmigrate/versions/008_version_1_5_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -import logging - -from sqlalchemy import * -from sqlalchemy.orm import joinedload - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_1_5_0 - #========================================================================== - # USER LOGS - #========================================================================== - - tbl = db_1_5_0.UserLog.__table__ - username = Column("username", String(255, convert_unicode=False, - assert_unicode=None), nullable=True, - unique=None, default=None) - # create username column - username.create(table=tbl) - - _Session = meta.Session() - ## after adding that column fix all usernames - users_log = _Session.query(db_1_5_0.UserLog)\ - .options(joinedload(db_1_5_0.UserLog.user))\ - .options(joinedload(db_1_5_0.UserLog.repository)).all() - - for entry in users_log: - entry.username = entry.user.username - _Session.add(entry) - _Session.commit() - - #alter username to not null - tbl_name = db_1_5_0.UserLog.__tablename__ - tbl = Table(tbl_name, - MetaData(bind=migrate_engine), autoload=True, - autoload_with=migrate_engine) - col = tbl.columns.username - - # remove nullability from revision field - col.alter(nullable=False) - - # issue fixups - fixups(db_1_5_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - # ** create default permissions ** # - #===================================== - for p in models.Permission.PERMS: - if not models.Permission.get_by_key(p[0]): - new_perm = models.Permission() - new_perm.permission_name = p[0] - new_perm.permission_longname = p[0] #translation err with p[1] - print 'Creating new permission %s' % p[0] - _SESSION().add(new_perm) - - _SESSION().commit() - - # ** populate default permissions ** # - #===================================== - - user = models.User.query().filter(models.User.username == 'default').scalar() - - def _make_perm(perm): - new_perm = models.UserToPerm() - new_perm.user = user - new_perm.permission = models.Permission.get_by_key(perm) - return new_perm - - def _get_group(perm_name): - return '.'.join(perm_name.split('.')[:1]) - - perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all() - defined_perms_groups = map(_get_group, - (x.permission.permission_name for x in perms)) - log.debug('GOT ALREADY DEFINED:%s', perms) - DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS - - # for every default permission that needs to be created, we check if - # it's group is already defined, if it's not we create default perm - for perm_name in DEFAULT_PERMS: - gr = _get_group(perm_name) - if gr not in defined_perms_groups: - log.debug('GR:%s not found, creating permission %s', - gr, perm_name) - new_perm = _make_perm(perm_name) - _SESSION().add(new_perm) - _SESSION().commit() - - # ** create default options ** # - #=============================== - skip_existing = True - for k, v in [ - ('default_repo_enable_locking', False), - ('default_repo_enable_downloads', False), - ('default_repo_enable_statistics', False), - ('default_repo_private', False), - ('default_repo_type', 'hg')]: - - if skip_existing and models.Setting.get_by_name(k) is not None: - log.debug('Skipping option %s', k) - continue - setting = models.Setting(k, v) - _SESSION().add(setting) - - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/009_version_1_5_1.py --- a/kallithea/lib/dbmigrate/versions/009_version_1_5_1.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - pass - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/010_version_1_5_2.py --- a/kallithea/lib/dbmigrate/versions/010_version_1_5_2.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_1_5_2 - #========================================================================== - # USER LOGS - #========================================================================== - tbl = db_1_5_2.UserIpMap.__table__ - tbl.create() - - #========================================================================== - # REPOSITORIES - #========================================================================== - tbl = db_1_5_2.Repository.__table__ - changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) - # create username column - changeset_cache.create(table=tbl) - - # issue fixups - fixups(db_1_5_2, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Upgrading repositories Caches') - repositories = models.Repository.getAll() - for repo in repositories: - print repo - repo.update_changeset_cache() - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/011_version_1_6_0.py --- a/kallithea/lib/dbmigrate/versions/011_version_1_6_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_1_6_0 - - #========================================================================== - # USER LOGS - #========================================================================== - tbl = db_1_6_0.RepositoryField.__table__ - tbl.create() - - # issue fixups - fixups(db_1_6_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Upgrading repositories Caches') - repositories = models.Repository.getAll() - for repo in repositories: - print repo - repo.update_changeset_cache() - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/012_version_1_7_0.py --- a/kallithea/lib/dbmigrate/versions/012_version_1_7_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_1_7_0 - - #========================================================================== - # UserUserGroupToPerm - #========================================================================== - tbl = db_1_7_0.UserUserGroupToPerm.__table__ - tbl.create() - - #========================================================================== - # UserGroupUserGroupToPerm - #========================================================================== - tbl = db_1_7_0.UserGroupUserGroupToPerm.__table__ - tbl.create() - - #========================================================================== - # Gist - #========================================================================== - tbl = db_1_7_0.Gist.__table__ - tbl.create() - - #========================================================================== - # UserGroup - #========================================================================== - tbl = db_1_7_0.UserGroup.__table__ - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), - nullable=True, unique=False, default=None) - # create username column - user_id.create(table=tbl) - - #========================================================================== - # RepoGroup - #========================================================================== - tbl = db_1_7_0.RepoGroup.__table__ - user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), - nullable=True, unique=False, default=None) - # create username column - user_id.create(table=tbl) - - # issue fixups - fixups(db_1_7_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - # ** create default permissions ** # - #===================================== - for p in models.Permission.PERMS: - if not models.Permission.get_by_key(p[0]): - new_perm = models.Permission() - new_perm.permission_name = p[0] - new_perm.permission_longname = p[0] #translation err with p[1] - _SESSION().add(new_perm) - - _SESSION().commit() - - # ** populate default permissions ** # - #===================================== - - user = models.User.query().filter(models.User.username == 'default').scalar() - - def _make_perm(perm): - new_perm = models.UserToPerm() - new_perm.user = user - new_perm.permission = models.Permission.get_by_key(perm) - return new_perm - - def _get_group(perm_name): - return '.'.join(perm_name.split('.')[:1]) - - perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all() - defined_perms_groups = map(_get_group, - (x.permission.permission_name for x in perms)) - log.debug('GOT ALREADY DEFINED:%s', perms) - DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS - - # for every default permission that needs to be created, we check if - # it's group is already defined, if it's not we create default perm - for perm_name in DEFAULT_PERMS: - gr = _get_group(perm_name) - if gr not in defined_perms_groups: - log.debug('GR:%s not found, creating permission %s', - gr, perm_name) - new_perm = _make_perm(perm_name) - _SESSION().add(new_perm) - _SESSION().commit() - - #fix all usergroups - - def _create_default_perms(user_group): - # create default permission - default_perm = 'usergroup.read' - def_user = models.User.get_default_user() - for p in def_user.user_perms: - if p.permission.permission_name.startswith('usergroup.'): - default_perm = p.permission.permission_name - break - - user_group_to_perm = models.UserUserGroupToPerm() - user_group_to_perm.permission = models.Permission.get_by_key(default_perm) - - user_group_to_perm.user_group = user_group - user_group_to_perm.user_id = def_user.user_id - return user_group_to_perm - - for ug in models.UserGroup.get_all(): - perm_obj = _create_default_perms(ug) - _SESSION().add(perm_obj) - _SESSION().commit() - - adm = models.User.get_first_admin() - # fix owners of UserGroup - for ug in _SESSION().query(models.UserGroup).all(): - ug.user_id = adm.user_id - _SESSION().add(ug) - _SESSION().commit() - - # fix owners of RepoGroup - for ug in _SESSION().query(models.RepoGroup).all(): - ug.user_id = adm.user_id - _SESSION().add(ug) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/013_version_1_7_0.py --- a/kallithea/lib/dbmigrate/versions/013_version_1_7_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - - - #========================================================================== - # UserGroup - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_7_0 import UserGroup - tbl = UserGroup.__table__ - user_id = tbl.columns.user_id - user_id.alter(nullable=False) - - #========================================================================== - # RepoGroup - #========================================================================== - from kallithea.lib.dbmigrate.schema.db_1_7_0 import RepoGroup - tbl = RepoGroup.__table__ - user_id = tbl.columns.user_id - user_id.alter(nullable=False) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/014_version_1_7_1.py --- a/kallithea/lib/dbmigrate/versions/014_version_1_7_1.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -import logging -import datetime - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_1_7_0 - - #========================================================================== - # Gist - #========================================================================== - tbl = db_1_7_0.Gist.__table__ - user_id = tbl.columns.gist_expires - user_id.alter(type=Float(53)) - - # issue fixups - fixups(db_1_7_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - # fix nullable columns on last_update - for r in models.Repository().get_all(): - if r.updated_on is None: - r.updated_on = datetime.datetime.fromtimestamp(0) - _SESSION().add(r) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/015_version_1_8_0.py --- a/kallithea/lib/dbmigrate/versions/015_version_1_8_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_1_8_0 - tbl = db_1_8_0.Setting.__table__ - app_settings_type = Column("app_settings_type", - String(255, convert_unicode=False, assert_unicode=None), - nullable=True, unique=None, default=None) - app_settings_type.create(table=tbl) - - # issue fixups - fixups(db_1_8_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Fixing default options now...') - - settings = [ - #general - ('realm', '', 'unicode'), - ('title', '', 'unicode'), - ('ga_code', '', 'unicode'), - ('show_public_icon', False, 'bool'), - ('show_private_icon', True, 'bool'), - ('stylify_metatags', True, 'bool'), - - # defaults - ('default_repo_enable_locking', False, 'bool'), - ('default_repo_enable_downloads', False, 'bool'), - ('default_repo_enable_statistics', False, 'bool'), - ('default_repo_private', False, 'bool'), - ('default_repo_type', 'hg', 'unicode'), - - #other - ('dashboard_items', 100, 'int'), - ('show_version', True, 'bool') - ] - - for name, default, type_ in settings: - setting = models.Setting.get_by_name(name) - if not setting: - # if we don't have this option create it - setting = models.Setting(name, default, type_) - - # fix certain key to new defaults - if name in ['title', 'show_public_icon']: - # change title if it's only the default - if name == 'title' and setting.app_settings_value == 'Kallithea': - setting.app_settings_value = default - else: - setting.app_settings_value = default - - setting._app_settings_type = type_ - _SESSION().add(setting) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/016_version_2_0_0.py --- a/kallithea/lib/dbmigrate/versions/016_version_2_0_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -import logging -import datetime - -from sqlalchemy import * - -from kallithea import EXTERN_TYPE_INTERNAL -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_0_0 - tbl = db_2_0_0.User.__table__ - - extern_type = Column("extern_type", - String(255, convert_unicode=False, assert_unicode=None), - nullable=True, unique=None, default=None) - extern_type.create(table=tbl) - - extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), - nullable=True, unique=None, default=None) - extern_name.create(table=tbl) - - created_on = Column('created_on', DateTime(timezone=False), - nullable=True, default=datetime.datetime.now) - created_on.create(table=tbl) - - # issue fixups - fixups(db_2_0_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Fixing default created on') - - for usr in models.User.get_all(): - usr.created_on = datetime.datetime.now() - _SESSION().add(usr) - _SESSION().commit() - - notify('Migrating LDAP attribute to extern') - for usr in models.User.get_all(): - ldap_dn = usr.ldap_dn - if ldap_dn: - usr.extern_name = ldap_dn - usr.extern_type = 'ldap' - else: - usr.extern_name = EXTERN_TYPE_INTERNAL - usr.extern_type = EXTERN_TYPE_INTERNAL - _SESSION().add(usr) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/017_version_2_0_0.py --- a/kallithea/lib/dbmigrate/versions/017_version_2_0_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -import logging -import datetime - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_0_0 - tbl = db_2_0_0.UserGroup.__table__ - - user_group_description = Column("user_group_description", - String(10000, convert_unicode=False, - assert_unicode=None), nullable=True, - unique=None, default=None) - user_group_description.create(table=tbl) - - created_on = Column('created_on', DateTime(timezone=False), - nullable=True, default=datetime.datetime.now) - created_on.create(table=tbl) - - # issue fixups - fixups(db_2_0_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Fixing default created on') - - for gr in models.UserGroup.get_all(): - gr.created_on = datetime.datetime.now() - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/018_version_2_0_0.py --- a/kallithea/lib/dbmigrate/versions/018_version_2_0_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * -from kallithea.lib.utils2 import str2bool - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_0_0 - - # issue fixups - fixups(db_2_0_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Fixing default auth modules') - plugins = 'kallithea.lib.auth_modules.auth_internal' - opts = [] - ldap_enabled = str2bool(getattr( - models.Setting.get_by_name('ldap_active'), - 'app_settings_value', False)) - if ldap_enabled: - plugins += ',kallithea.lib.auth_modules.auth_ldap' - opts.append(('auth_ldap_enabled', 'True', 'bool')) - - opts.append(('auth_plugins', plugins, 'list'),) - opts.append(('auth_internal_enabled', 'True', 'bool')) - - for name, default, type_ in opts: - setting = models.Setting.get_by_name(name) - if not setting: - # if we don't have this option create it - setting = models.Setting(name, default, type_) - - _SESSION().add(setting) - _SESSION().commit() - - #copy over the LDAP settings - old_ldap = [('ldap_active', 'false', 'bool'), ('ldap_host', '', 'unicode'), - ('ldap_port', '389', 'int'), ('ldap_tls_kind', 'PLAIN', 'unicode'), - ('ldap_tls_reqcert', '', 'unicode'), ('ldap_dn_user', '', 'unicode'), - ('ldap_dn_pass', '', 'unicode'), ('ldap_base_dn', '', 'unicode'), - ('ldap_filter', '', 'unicode'), ('ldap_search_scope', '', 'unicode'), - ('ldap_attr_login', '', 'unicode'), ('ldap_attr_firstname', '', 'unicode'), - ('ldap_attr_lastname', '', 'unicode'), ('ldap_attr_email', '', 'unicode')] - for k, v, t in old_ldap: - old_setting = models.Setting.get_by_name(k) - name = 'auth_%s' % k - setting = models.Setting.get_by_name(name) - if setting is None: - # if we don't have this option create it - if old_setting is not None: - v = old_setting.app_settings_value - setting = models.Setting(name, v, t) - - _SESSION().add(setting) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/019_version_2_0_0.py --- a/kallithea/lib/dbmigrate/versions/019_version_2_0_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_0_0 - tbl = db_2_0_0.Setting.__table__ - settings_value = tbl.columns.app_settings_value - settings_value.alter(type=String(4096)) - - # issue fixups - fixups(db_2_0_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - return diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/020_version_2_0_1.py --- a/kallithea/lib/dbmigrate/versions/020_version_2_0_1.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea import EXTERN_TYPE_INTERNAL -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_0_1 - - # issue fixups - fixups(db_2_0_1, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - #fix all empty extern type users to default 'internal' - for usr in models.User.query().all(): - if not usr.extern_name: - usr.extern_name = EXTERN_TYPE_INTERNAL - usr.extern_type = EXTERN_TYPE_INTERNAL - _SESSION().add(usr) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/021_version_2_0_2.py --- a/kallithea/lib/dbmigrate/versions/021_version_2_0_2.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -import os -import logging -import datetime - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_0_1 - tbl = db_2_0_1.RepoGroup.__table__ - - created_on = Column('created_on', DateTime(timezone=False), nullable=True, - default=datetime.datetime.now) - created_on.create(table=tbl) - - #fix null values on certain columns when upgrading from older releases - tbl = db_2_0_1.UserLog.__table__ - col = tbl.columns.user_id - col.alter(nullable=True) - - tbl = db_2_0_1.UserFollowing.__table__ - col = tbl.columns.follows_repository_id - col.alter(nullable=True) - - tbl = db_2_0_1.UserFollowing.__table__ - col = tbl.columns.follows_user_id - col.alter(nullable=True) - - # issue fixups - fixups(db_2_0_1, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Fixing default created on for repo groups') - - for gr in models.RepoGroup.get_all(): - gr.created_on = datetime.datetime.now() - _SESSION().add(gr) - _SESSION().commit() - - repo_store_path = models.Ui.get_repos_location() - _store = os.path.join(repo_store_path, '.cache', 'largefiles') - notify('Setting largefiles usercache') - print _store - - if not models.Ui.get_by_key('usercache'): - largefiles = models.Ui() - largefiles.ui_section = 'largefiles' - largefiles.ui_key = 'usercache' - largefiles.ui_value = _store - _SESSION().add(largefiles) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/022_version_2_0_2.py --- a/kallithea/lib/dbmigrate/versions/022_version_2_0_2.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_0_2 - - # issue fixups - fixups(db_2_0_2, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('fixing new schema for landing_rev') - - for repo in models.Repository.get_all(): - print u'repo %s old landing rev is: %s' % (repo, repo.landing_rev) - _rev = repo.landing_rev[1] - _rev_type = 'rev' # default - - if _rev in ['default', 'master']: - _rev_type = 'branch' - elif _rev in ['tip']: - _rev_type = 'rev' - else: - try: - scm = repo.scm_instance - if scm: - known_branches = scm.branches.keys() - known_bookmarks = scm.bookmarks.keys() - if _rev in known_branches: - _rev_type = 'branch' - elif _rev in known_bookmarks: - _rev_type = 'book' - except Exception as e: - print e - print 'continue...' - #we don't want any error to break the process - pass - - _new_landing_rev = '%s:%s' % (_rev_type, _rev) - print u'setting to %s' % _new_landing_rev - repo.landing_rev = _new_landing_rev - _SESSION().add(repo) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/023_version_2_1_0.py --- a/kallithea/lib/dbmigrate/versions/023_version_2_1_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_1_0 - - tbl = db_2_1_0.UserApiKeys.__table__ - tbl.create() - - # issue fixups - fixups(db_2_1_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - pass diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/024_version_2_1_0.py --- a/kallithea/lib/dbmigrate/versions/024_version_2_1_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -from kallithea.lib.utils2 import str2bool - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_1_0 - - # issue fixups - fixups(db_2_1_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - from pylons import config - - notify('migrating options from .ini file') - use_gravatar = str2bool(config.get('use_gravatar')) - print('Setting gravatar use to: %s' % use_gravatar) - sett = models.Setting.create_or_update('use_gravatar', - use_gravatar, 'bool') - _SESSION().add(sett) - _SESSION.commit() - #set the new format of gravatar URL - gravatar_url = models.User.DEFAULT_GRAVATAR_URL - if config.get('alternative_gravatar_url'): - gravatar_url = config.get('alternative_gravatar_url') - - print('Setting gravatar url to:%s' % gravatar_url) - sett = models.Setting.create_or_update('gravatar_url', - gravatar_url, 'unicode') - _SESSION().add(sett) - _SESSION.commit() - - #now create new changed value of clone_url - clone_uri_tmpl = models.Repository.DEFAULT_CLONE_URI - print('settings new clone url template to %s' % clone_uri_tmpl) - - sett = models.Setting.create_or_update('clone_uri_tmpl', - clone_uri_tmpl, 'unicode') - _SESSION().add(sett) - _SESSION.commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/025_version_2_1_0.py --- a/kallithea/lib/dbmigrate/versions/025_version_2_1_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_1_0 - - # issue fixups - fixups(db_2_1_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Creating upgrade URL') - sett = models.Setting.create_or_update('update_url', - models.Setting.DEFAULT_UPDATE_URL, 'unicode') - _SESSION().add(sett) - _SESSION.commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/026_version_2_2_0.py --- a/kallithea/lib/dbmigrate/versions/026_version_2_2_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_2_0 - - tbl = db_2_2_0.User.__table__ - - user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data - user_data.create(table=tbl) - - # issue fixups - fixups(db_2_2_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - pass diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/027_version_2_2_0.py --- a/kallithea/lib/dbmigrate/versions/027_version_2_2_0.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_2_0 - - # issue fixups - fixups(db_2_2_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - # ** create default permissions ** # - #===================================== - for p in models.Permission.PERMS: - if not models.Permission.get_by_key(p[0]): - new_perm = models.Permission() - new_perm.permission_name = p[0] - new_perm.permission_longname = p[0] #translation err with p[1] - print 'Creating new permission %s' % p[0] - _SESSION().add(new_perm) - - _SESSION().commit() - - # ** set default create_on_write to active - user = models.User.get_default_user() - _def = 'hg.create.write_on_repogroup.true' - new = models.UserToPerm() - new.user = user - new.permission = models.Permission.get_by_key(_def) - print 'Setting default to %s' % _def - _SESSION().add(new) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/028_version_2_2_3.py --- a/kallithea/lib/dbmigrate/versions/028_version_2_2_3.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_2_0 - - tbl = db_2_2_0.UserGroup.__table__ - - user_data = Column("group_data", LargeBinary(), nullable=True) # JSON data - user_data.create(table=tbl) - - # issue fixups - fixups(db_2_2_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - pass diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/029_version_2_2_3.py --- a/kallithea/lib/dbmigrate/versions/029_version_2_2_3.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_2_0 - - # issue fixups - fixups(db_2_2_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Adding grid items options now...') - - settings = [ - ('admin_grid_items', 25, 'int'), # old hardcoded value was 25 - ] - - for name, default, type_ in settings: - setting = models.Setting.get_by_name(name) - if not setting: - # if we don't have this option create it - setting = models.Setting(name, default, type_) - setting._app_settings_type = type_ - _SESSION().add(setting) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/030_version_2_2_3.py --- a/kallithea/lib/dbmigrate/versions/030_version_2_2_3.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_2_0 - - tbl = db_2_2_0.Repository.__table__ - - repo_state = Column("repo_state", String(255), nullable=True) - repo_state.create(table=tbl) - - # issue fixups - fixups(db_2_2_0, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - pass diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/031_version_2_2_3.py --- a/kallithea/lib/dbmigrate/versions/031_version_2_2_3.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -import logging - -from sqlalchemy import * - -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta -from kallithea.lib.dbmigrate.versions import _reset_base, notify - -log = logging.getLogger(__name__) - - -def upgrade(migrate_engine): - """ - Upgrade operations go here. - Don't create your own engine; bind migrate_engine to your metadata - """ - _reset_base(migrate_engine) - from kallithea.lib.dbmigrate.schema import db_2_2_3 - - # issue fixups - fixups(db_2_2_3, meta.Session) - - -def downgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - -def fixups(models, _SESSION): - notify('Creating repository states') - for repo in models.Repository.get_all(): - _state = models.Repository.STATE_CREATED - print 'setting repo %s state to "%s"' % (repo, _state) - repo.repo_state = _state - _SESSION().add(repo) - _SESSION().commit() diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/dbmigrate/versions/__init__.py --- a/kallithea/lib/dbmigrate/versions/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -""" -kallithea.lib.dbmigrate.versions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Package containing new versions of database models - -This file was forked by the Kallithea project in July 2014. -Original author and date, and relevant copyright and licensing information is below: -:created_on: Dec 11, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH, and others. -:license: GPLv3, see LICENSE.md for more details. -""" - -from sqlalchemy import * -from sqlalchemy.ext.declarative import declarative_base -from kallithea.lib.dbmigrate.migrate import * -from kallithea.lib.dbmigrate.migrate.changeset import * - -from kallithea.model import meta - - -def notify(msg, caps=True): - """ - Notification for migrations messages - """ - ml = len(msg) + (4 * 2) - formatted_msg = ('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)) - if caps: - formatted_msg = formatted_msg.upper() - print(formatted_msg) - - -def _reset_base(migrate_engine): - ## RESET COMPLETLY THE metadata for sqlalchemy to use previous declared Base - Base = declarative_base() - Base.metadata.clear() - Base.metadata = MetaData() - Base.metadata.bind = migrate_engine - - # new session and base - #meta.Session = scoped_session(sessionmaker(expire_on_commit=True,)) - #meta.Session.configure(bind=migrate_engine) - meta.Base = Base - - notify('SQLA BASE RESET !') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/diffs.py --- a/kallithea/lib/diffs.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/diffs.py Sat Dec 24 00:34:38 2016 +0100 @@ -62,27 +62,34 @@ if filenode_old is None: filenode_old = FileNode(filenode_new.path, '', EmptyChangeset()) + op = None + a_path = filenode_old.path # default, might be overriden by actual rename in diff if filenode_old.is_binary or filenode_new.is_binary: diff = wrap_to_table(_('Binary file')) stats = (0, 0) - size = 0 - elif cut_off_limit != -1 and (cut_off_limit is None or - (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)): + elif cut_off_limit != -1 and ( + cut_off_limit is None or + (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)): f_gitdiff = get_gitdiff(filenode_old, filenode_new, ignore_whitespace=ignore_whitespace, context=line_context) diff_processor = DiffProcessor(f_gitdiff, format='gitdiff') + _parsed = diff_processor.prepare() + if _parsed: # there should be exactly one element, for the specified file + f = _parsed[0] + op = f['operation'] + a_path = f['old_filename'] diff = diff_processor.as_html(enable_comments=enable_comments) stats = diff_processor.stat() - size = len(diff or '') + else: diff = wrap_to_table(_('Changeset was too big and was cut off, use ' 'diff menu to display this diff')) stats = (0, 0) - size = 0 + if not diff: submodules = filter(lambda o: isinstance(o, SubModuleNode), [filenode_new, filenode_old]) @@ -94,7 +101,7 @@ cs1 = filenode_old.changeset.raw_id cs2 = filenode_new.changeset.raw_id - return size, cs1, cs2, diff, stats + return cs1, cs2, a_path, diff, stats, op def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3): @@ -283,15 +290,13 @@ self.removes += 1 return safe_unicode(l) - def _highlight_line_difflib(self, line, next_): + def _highlight_line_difflib(self, old, new): """ Highlight inline changes in both lines. """ - if line['action'] == 'del': - old, new = line, next_ - else: - old, new = next_, line + assert old['action'] == 'del' + assert new['action'] == 'add' oldwords = self._token_re.split(old['line']) newwords = self._token_re.split(new['line']) @@ -364,7 +369,7 @@ groups = match.groupdict() rest = diff_chunk[match.end():] if rest and not rest.startswith('@') and not rest.startswith('literal ') and not rest.startswith('delta '): - raise Exception('cannot parse diff header: %r followed by %r' % (diff_chunk[:match.end()], rest[:1000])) + raise Exception('cannot parse %s diff header: %r followed by %r' % (self.vcs, diff_chunk[:match.end()], rest[:1000])) difflines = imap(self._escaper, re.findall(r'.*\n|.+$', rest)) # don't split on \r as str.splitlines do return groups, difflines @@ -473,6 +478,7 @@ if _op not in [MOD_FILENODE]]) _files.append({ + 'old_filename': head['a_path'], 'filename': head['b_path'], 'old_revision': head['a_blob_id'], 'new_revision': head['b_blob_id'], @@ -484,19 +490,34 @@ if not inline_diff: return diff_container(_files) - # highlight inline changes + # highlight inline changes when one del is followed by one add for diff_data in _files: for chunk in diff_data['chunks']: lineiter = iter(chunk) try: - while 1: - line = lineiter.next() - if line['action'] not in ['unmod', 'context']: - nextline = lineiter.next() - if nextline['action'] in ['unmod', 'context'] or \ - nextline['action'] == line['action']: - continue - self.differ(line, nextline) + peekline = lineiter.next() + while True: + # find a first del line + while peekline['action'] != 'del': + peekline = lineiter.next() + delline = peekline + peekline = lineiter.next() + # if not followed by add, eat all following del lines + if peekline['action'] != 'add': + while peekline['action'] == 'del': + peekline = lineiter.next() + continue + # found an add - make sure it is the only one + addline = peekline + try: + peekline = lineiter.next() + except StopIteration: + # add was last line - ok + self.differ(delline, addline) + raise + if peekline['action'] != 'add': + # there was only one add line - ok + self.differ(delline, addline) except StopIteration: pass @@ -625,7 +646,7 @@ def prepare(self, inline_diff=True): """ - Prepare the passed udiff for HTML rendering. It'l return a list + Prepare the passed udiff for HTML rendering. It'll return a list of dicts with diff information """ parsed = self._parser(inline_diff=inline_diff) @@ -710,7 +731,7 @@ }) _html.append('''%(link)s''' % { - 'link': _link_to_if(True, change['old_lineno'], + 'link': _link_to_if(not no_lineno, change['old_lineno'], '#%s' % anchor_old) }) _html.append('''\n''') diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/graphmod.py --- a/kallithea/lib/graphmod.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/graphmod.py Sat Dec 24 00:34:38 2016 +0100 @@ -21,9 +21,14 @@ def _first_known_ancestors(parentrev_func, minrev, knownrevs, head): """ - Walk DAG defined by parentrev_func. - Return most immediate ancestors of head that are in knownrevs. - minrev is lower bound on knownrevs. + Return the apparent parents of the head revision in a filtered DAG. + This is like calling plain parentrev_func, except that if the parent has + been filtered (isn't in knownrevs), recurse on its parents. + For ancestors that are unknown in knownrevs, the revision number is negated. + (This means that a Mercurial revision can have more than 2 "parents".) + + parentrev_func defines the full DAG. + knownrevs contains the subset we care about. minrev is lower bound on knownrevs. """ pending = set([head]) seen = set() @@ -57,6 +62,7 @@ return list(_colored(repo, dag)) def _dagwalker(repo, revs): + """Iterate over revs, yielding revs (highest first) and parents to show in the graph.""" if not revs: return @@ -66,7 +72,7 @@ def parentrev_func(rev): return [x.revision for x in repo[rev].parents] - minrev = revs[-1] # assuming sorted reverse + minrev = revs[-1] # assuming sorted with highest revision numbers first knownrevs = set(revs) acache = {} for rev in revs: @@ -97,14 +103,17 @@ """ branch_cache = {} def branch(rev): + """Return branch for rev, using cache for efficiency. + For Mercurial, always return the named branch name (which may be 'default'). + For Git, return a branch name for branch heads, otherwise None.""" if rev not in branch_cache: branch_cache[rev] = repo[rev].branch return branch_cache[rev] - row = [] - colors = {} - obs = {} - newcolor = 1 + row = [] # the ancestor revision that each column is tracking + colors = {} # color number for revisions - set by descendants + obs = {} # obsolete flag for revisions - set by descendants + newcolor = 1 # the next available color for (rev, dagparents) in dag: @@ -121,24 +130,26 @@ # Add unknown parents to nextrow tmprow = row[:] tmprow[col:col + 1] = reversed(addparents) # highest revs first (to the right), dead ends last (to the left) - # Stop looking for non-existing ancestors + # Filter old fake parents but keep new ones nextrow = [] for r in tmprow: - if r > nullrev or r in dagparents: + if r >= 0 or r in dagparents: nextrow.append(r) else: colors.pop(r) obs.pop(r) - # Set colors for the parents + # Let color and obs for this rev be "inherited" by the first "parent" color = colors.pop(rev) if addparents: b = branch(rev) + searching = True for p in reversed(addparents): obs[p] = int(repo[p].obsolete) - if b and branch(abs(p)) == b: + if searching and branch(abs(p)) in [b, None]: + # This is the first parent on the same branch - inherit the color colors[p] = color - b = None + searching = False # make sure we don't give the color away twice else: colors[p] = newcolor newcolor += 1 @@ -155,5 +166,9 @@ # Yield and move on closing = int(repo[rev].closesbranch) obsolete = int(repo[rev].obsolete) - yield ((col, color), edges, closing, obsolete) + bumped = int(repo[rev].bumped) + divergent = int(repo[rev].divergent) + extinct = int(repo[rev].extinct) + unstable = int(repo[rev].unstable) + yield ((col, color), edges, closing, obsolete, bumped, divergent, extinct, unstable) row = nextrow diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/helpers.py --- a/kallithea/lib/helpers.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/helpers.py Sat Dec 24 00:34:38 2016 +0100 @@ -19,49 +19,34 @@ """ import hashlib import StringIO -import math import logging import re import urlparse import textwrap +from beaker.cache import cache_region from pygments.formatters.html import HtmlFormatter from pygments import highlight as code_highlight -from pylons import url -from pylons.i18n.translation import _, ungettext +from pylons.i18n.translation import _ from webhelpers.html import literal, HTML, escape -from webhelpers.html.tools import * -from webhelpers.html.builder import make_tag -from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ - end_form, file, hidden, image, javascript_link, link_to, \ - link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \ - submit, text, password, textarea, title, ul, xml_declaration, radio, \ - form as insecure_form -from webhelpers.html.tools import auto_link, button_to, highlight, \ - js_obfuscate, mail_to, strip_links, strip_tags, tag_re -from webhelpers.number import format_byte_size, format_bit_size +from webhelpers.html.tags import checkbox, end_form, hidden, link_to, \ + select, submit, text, password, textarea, radio, form as insecure_form +from webhelpers.number import format_byte_size from webhelpers.pylonslib import Flash as _Flash from webhelpers.pylonslib.secure_form import secure_form, authentication_token -from webhelpers.text import chop_at, collapse, convert_accented_entities, \ - convert_misc_entities, lchop, plural, rchop, remove_formatting, \ - replace_whitespace, urlify, truncate, wrap_paragraphs -from webhelpers.date import time_ago_in_words -from webhelpers.paginate import Page as _Page +from webhelpers.text import chop_at, truncate, wrap_paragraphs from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \ convert_boolean_attrs, NotGiven, _make_safe_id_component +from kallithea.config.routing import url from kallithea.lib.annotate import annotate_highlight -from kallithea.lib.utils import repo_name_slug, get_custom_lexer +from kallithea.lib.pygmentsutils import get_custom_lexer from kallithea.lib.utils2 import str2bool, safe_unicode, safe_str, \ - get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\ - safe_int -from kallithea.lib.markup_renderer import MarkupRenderer, url_re + time_to_datetime, AttributeDict, safe_int, MENTIONS_REGEX +from kallithea.lib.markup_renderer import url_re from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset -from kallithea.config.conf import DATE_FORMAT, DATETIME_FORMAT -from kallithea.model.changeset_status import ChangesetStatusModel -from kallithea.model.db import URL_SEP, Permission log = logging.getLogger(__name__) @@ -100,8 +85,12 @@ .replace("'", "'") ) -def shorter(s, size=20): - postfix = '...' +def shorter(s, size=20, firstline=False, postfix='...'): + """Truncate s to size, including the postfix string if truncating. + If firstline, truncate at newline. + """ + if firstline: + s = s.split('\n', 1)[0].rstrip() if len(s) > size: return s[:size - len(postfix)] + postfix return s @@ -132,22 +121,6 @@ return 'C-%s-%s' % (short_id(raw_id), hashlib.md5(safe_str(path)).hexdigest()[:12]) -class _GetError(object): - """Get error from form_errors, and represent it as span wrapped error - message - - :param field_name: field to fetch errors for - :param form_errors: form errors dict - """ - - def __call__(self, field_name, form_errors): - tmpl = """%s""" - if form_errors and field_name in form_errors: - return literal(tmpl % form_errors.get(field_name)) - -get_error = _GetError() - - class _FilesBreadCrumbs(object): def __call__(self, repo_name, rev, paths): @@ -348,8 +321,9 @@ url('changeset_home', repo_name=repo_name, revision=changeset.raw_id), style=get_color_string(changeset.raw_id), - class_='tooltip safe-html-title', - title=tooltip_html + class_='safe-html-title', + title=tooltip_html, + **{'data-toggle': 'tooltip'} ) uri += '\n' @@ -359,10 +333,6 @@ return literal(markup_whitespace(annotate_highlight(filenode, url_func(repo_name), **kwargs))) -def is_following_repo(repo_name, user_id): - from kallithea.model.scm import ScmModel - return ScmModel().is_following_repo(repo_name, user_id) - class _Message(object): """A message returned by ``Flash.pop_messages()``. @@ -423,7 +393,6 @@ #============================================================================== from kallithea.lib.vcs.utils import author_name, author_email from kallithea.lib.utils2 import credentials_filter, age as _age -from kallithea.model.db import User, ChangesetStatus, PullRequest age = lambda x, y=False: _age(x, y) capitalize = lambda x: x.capitalize() @@ -477,20 +446,18 @@ return _type == 'hg' +@cache_region('long_term', 'user_or_none') def user_or_none(author): + """Try to match email part of VCS committer string with a local user - or return None""" + from kallithea.model.db import User email = author_email(author) if email: - user = User.get_by_email(email, case_insensitive=True, cache=True) - if user is not None: - return user - - user = User.get_by_username(author_name(author), case_insensitive=True, cache=True) - if user is not None: - return user - + return User.get_by_email(email, cache=True) # cache will only use sql_cache_short return None def email_or_none(author): + """Try to match email part of VCS committer string with a local user. + Return primary email of user, email part of the specified author name, or None.""" if not author: return None user = user_or_none(author) @@ -508,6 +475,7 @@ def person(author, show_attr="username"): """Find the user identified by 'author', return one of the users attributes, default to the username attribute, None if there is no user""" + from kallithea.model.db import User # attr to return from fetched user person_getter = lambda usr: getattr(usr, show_attr) @@ -524,6 +492,7 @@ def person_by_id(id_, show_attr="username"): + from kallithea.model.db import User # attr to return from fetched user person_getter = lambda usr: getattr(usr, show_attr) @@ -536,29 +505,6 @@ return id_ -def desc_stylize(value): - """ - converts tags from value into html equivalent - - :param value: - """ - if not value: - return '' - - 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-Z0-9\-\/]*)\]', - '
\\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 boolicon(value): """Returns boolean value of a value, represented as small html image of true/false icons @@ -620,7 +566,7 @@ else: # changeset cannot be found - it might have been stripped or removed lbl = rev[:12] - title_ = _('Changeset not found') + title_ = _('Changeset %s not found') % lbl if parse_cs: return link_to(lbl, url_, title=title_, class_='tooltip') return link_to(lbl, url_, raw_id=rev.raw_id, repo_name=repo_name, @@ -667,7 +613,7 @@ _rev = '%s...%s' % (_name1, _name2) compare_view = ( - '
' + '
' '%s
' % ( _('Show all combined changesets %s->%s') % ( revs_ids[0][:12], revs_ids[-1][:12] @@ -731,6 +677,7 @@ return group_name def get_pull_request(): + from kallithea.model.db import PullRequest pull_request_id = action_params nice_id = PullRequest.make_nice_id(pull_request_id) @@ -802,9 +749,9 @@ if feed: action = action_str[0].replace('[', '').replace(']', '') else: - action = action_str[0]\ - .replace('[', '')\ - .replace(']', '') + action = action_str[0] \ + .replace('[', '') \ + .replace(']', '') action_params_func = lambda: "" @@ -831,15 +778,33 @@ #============================================================================== # PERMS #============================================================================== -from kallithea.lib.auth import HasPermissionAny, HasPermissionAll, \ -HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \ -HasRepoGroupPermissionAny +from kallithea.lib.auth import HasPermissionAny, \ + HasRepoPermissionAny, HasRepoGroupPermissionAny #============================================================================== # GRAVATAR URL #============================================================================== -def gravatar(email_address, cls='', size=30, ssl_enabled=True): +def gravatar_div(email_address, cls='', size=30, **div_attributes): + """Return an html literal with a div around a gravatar if they are enabled. + Extra keyword parameters starting with 'div_' will get the prefix removed + and '_' changed to '-' and be used as attributes on the div. The default + class is 'gravatar'. + """ + from pylons import tmpl_context as c + if not c.visual.use_gravatar: + return '' + if 'div_class' not in div_attributes: + div_attributes['div_class'] = "gravatar" + attributes = [] + for k, v in sorted(div_attributes.items()): + assert k.startswith('div_'), k + attributes.append(' %s="%s"' % (k[4:].replace('_', '-'), escape(v))) + return literal("""%s
""" % + (''.join(attributes), + gravatar(email_address, cls=cls, size=size))) + +def gravatar(email_address, cls='', size=30): """return html element of the gravatar This method will return an with the resolution double the size (for @@ -847,268 +812,47 @@ empty then we fallback to using an icon. """ - src = gravatar_url(email_address, size*2, ssl_enabled) + from pylons import tmpl_context as c + if not c.visual.use_gravatar: + return '' - # here it makes sense to use style="width: ..." (instead of, say, a - # stylesheet) because we using this to generate a high-res (retina) size - tmpl = '' + src = gravatar_url(email_address, size * 2) - # if src is empty then there was no gravatar, so we use a font icon - if not src: - tmpl = """""" - - tmpl = tmpl.format(cls=cls, size=size, src=src) - return literal(tmpl) + if src: + # here it makes sense to use style="width: ..." (instead of, say, a + # stylesheet) because we using this to generate a high-res (retina) size + html = ('' + .format(cls=cls, size=size, src=src)) -def gravatar_url(email_address, size=30, ssl_enabled=True): - # doh, we need to re-import those to mock it later - from pylons import url - from pylons import tmpl_context as c + else: + # if src is empty then there was no gravatar, so we use a font icon + html = ("""""" + .format(cls=cls, size=size, src=src)) + + return literal(html) - _def = 'anonymous@kallithea-scm.org' # default gravatar - _use_gravatar = c.visual.use_gravatar - _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL - - email_address = email_address or _def - - if not _use_gravatar or not email_address or email_address == _def: +def gravatar_url(email_address, size=30, default=''): + # doh, we need to re-import those to mock it later + from kallithea.config.routing import url + from kallithea.model.db import User + from pylons import tmpl_context as c + if not c.visual.use_gravatar: return "" - if _use_gravatar: - _md5 = lambda s: hashlib.md5(s).hexdigest() - - tmpl = _gravatar_url - parsed_url = urlparse.urlparse(url.current(qualified=True)) - tmpl = tmpl.replace('{email}', email_address)\ - .replace('{md5email}', _md5(safe_str(email_address).lower())) \ - .replace('{netloc}', parsed_url.netloc)\ - .replace('{scheme}', parsed_url.scheme)\ - .replace('{size}', safe_str(size)) - return tmpl - -class Page(_Page): - """ - Custom pager to match rendering style with YUI paginator - """ - - def _get_pos(self, cur_page, max_page, items): - edge = (items / 2) + 1 - if (cur_page <= edge): - radius = max(items / 2, items - cur_page) - elif (max_page - cur_page) < edge: - radius = (items - 1) - (max_page - cur_page) - else: - radius = items / 2 - - left = max(1, (cur_page - (radius))) - right = min(max_page, cur_page + (radius)) - return left, cur_page, right - - def _range(self, regexp_match): - """ - Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8'). - - Arguments: - - regexp_match - A "re" (regular expressions) match object containing the - radius of linked pages around the current page in - regexp_match.group(1) as a string - - This function is supposed to be called as a callable in - re.sub. - - """ - radius = int(regexp_match.group(1)) - - # Compute the first and last page number within the radius - # e.g. '1 .. 5 6 [7] 8 9 .. 12' - # -> leftmost_page = 5 - # -> rightmost_page = 9 - leftmost_page, _cur, rightmost_page = self._get_pos(self.page, - self.last_page, - (radius * 2) + 1) - nav_items = [] + _def = 'anonymous@kallithea-scm.org' # default gravatar + email_address = email_address or _def - # Create a link to the first page (unless we are on the first page - # or there would be no need to insert '..' spacers) - if self.page != self.first_page and self.first_page < leftmost_page: - nav_items.append(self._pagerlink(self.first_page, self.first_page)) - - # Insert dots if there are pages between the first page - # and the currently displayed page range - if leftmost_page - self.first_page > 1: - # Wrap in a SPAN tag if nolink_attr is set - text_ = '..' - if self.dotdot_attr: - text_ = HTML.span(c=text_, **self.dotdot_attr) - nav_items.append(text_) - - for thispage in xrange(leftmost_page, rightmost_page + 1): - # Highlight the current page number and do not use a link - text_ = str(thispage) - if thispage == self.page: - # Wrap in a SPAN tag if nolink_attr is set - if self.curpage_attr: - text_ = HTML.span(c=text_, **self.curpage_attr) - nav_items.append(text_) - # Otherwise create just a link to that page - else: - nav_items.append(self._pagerlink(thispage, text_)) - - # Insert dots if there are pages between the displayed - # page numbers and the end of the page range - if self.last_page - rightmost_page > 1: - text_ = '..' - # Wrap in a SPAN tag if nolink_attr is set - if self.dotdot_attr: - text_ = HTML.span(c=text_, **self.dotdot_attr) - nav_items.append(text_) - - # Create a link to the very last page (unless we are on the last - # page or there would be no need to insert '..' spacers) - if self.page != self.last_page and rightmost_page < self.last_page: - nav_items.append(self._pagerlink(self.last_page, self.last_page)) - - #_page_link = url.current() - #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) - #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) - return self.separator.join(nav_items) - - def pager(self, format='~2~', page_param='page', partial_param='partial', - show_if_single_page=False, separator=' ', onclick=None, - symbol_first='<<', symbol_last='>>', - symbol_previous='<', symbol_next='>', - link_attr={'class': 'pager_link', 'rel': 'prerender'}, - curpage_attr={'class': 'pager_curpage'}, - dotdot_attr={'class': 'pager_dotdot'}, **kwargs): - - self.curpage_attr = curpage_attr - self.separator = separator - self.pager_kwargs = kwargs - self.page_param = page_param - self.partial_param = partial_param - self.onclick = onclick - self.link_attr = link_attr - self.dotdot_attr = dotdot_attr + if email_address == _def: + return default - # Don't show navigator if there is no more than one page - if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): - return '' - - from string import Template - # Replace ~...~ in token format by range of pages - result = re.sub(r'~(\d+)~', self._range, format) - - # Interpolate '%' variables - result = Template(result).safe_substitute({ - 'first_page': self.first_page, - 'last_page': self.last_page, - 'page': self.page, - 'page_count': self.page_count, - 'items_per_page': self.items_per_page, - 'first_item': self.first_item, - 'last_item': self.last_item, - 'item_count': self.item_count, - 'link_first': self.page > self.first_page and \ - self._pagerlink(self.first_page, symbol_first) or '', - 'link_last': self.page < self.last_page and \ - self._pagerlink(self.last_page, symbol_last) or '', - 'link_previous': self.previous_page and \ - self._pagerlink(self.previous_page, symbol_previous) \ - or HTML.span(symbol_previous, class_="yui-pg-previous"), - 'link_next': self.next_page and \ - self._pagerlink(self.next_page, symbol_next) \ - or HTML.span(symbol_next, class_="yui-pg-next") - }) - - return literal(result) - - -#============================================================================== -# REPO PAGER, PAGER FOR REPOSITORY -#============================================================================== -class RepoPage(Page): - - def __init__(self, collection, page=1, items_per_page=20, - item_count=None, url=None, **kwargs): - - """Create a "RepoPage" instance. special pager for paging - repository - """ - self._url_generator = url - - # Safe the kwargs class-wide so they can be used in the pager() method - self.kwargs = kwargs - - # Save a reference to the collection - self.original_collection = collection - - self.collection = collection - - # The self.page is the number of the current page. - # The first page has the number 1! - try: - self.page = int(page) # make it int() if we get it as a string - except (ValueError, TypeError): - self.page = 1 - - self.items_per_page = items_per_page - - # Unless the user tells us how many items the collections has - # we calculate that ourselves. - if item_count is not None: - self.item_count = item_count - else: - self.item_count = len(self.collection) - - # Compute the number of the first and last available page - if self.item_count > 0: - self.first_page = 1 - self.page_count = int(math.ceil(float(self.item_count) / - self.items_per_page)) - self.last_page = self.first_page + self.page_count - 1 - - # Make sure that the requested page number is the range of - # valid pages - if self.page > self.last_page: - self.page = self.last_page - elif self.page < self.first_page: - self.page = self.first_page - - # Note: the number of items on this page can be less than - # items_per_page if the last page is not full - self.first_item = max(0, (self.item_count) - (self.page * - items_per_page)) - self.last_item = ((self.item_count - 1) - items_per_page * - (self.page - 1)) - - self.items = list(self.collection[self.first_item:self.last_item + 1]) - - # Links to previous and next page - if self.page > self.first_page: - self.previous_page = self.page - 1 - else: - self.previous_page = None - - if self.page < self.last_page: - self.next_page = self.page + 1 - else: - self.next_page = None - - # No items available - else: - self.first_page = None - self.page_count = 0 - self.last_page = None - self.first_item = None - self.last_item = None - self.previous_page = None - self.next_page = None - self.items = [] - - # This is a subclass of the 'list' type. Initialise the list now. - list.__init__(self, reversed(self.items)) + parsed_url = urlparse.urlparse(url.current(qualified=True)) + url = (c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL ) \ + .replace('{email}', email_address) \ + .replace('{md5email}', hashlib.md5(safe_str(email_address).lower()).hexdigest()) \ + .replace('{netloc}', parsed_url.netloc) \ + .replace('{scheme}', parsed_url.scheme) \ + .replace('{size}', safe_str(size)) + return url def changed_tooltip(nodes): @@ -1129,27 +873,6 @@ return ': ' + _('No files') -def repo_link(groups_and_repos): - """ - Makes a breadcrumbs link to repo within a group - joins » on each group to create a fancy link - - ex:: - group >> subgroup >> repo - - :param groups_and_repos: - :param last_url: - """ - groups, just_name, repo_name = groups_and_repos - last_url = url('summary_home', repo_name=repo_name) - last_link = link_to(just_name, last_url) - - def make_link(group): - return link_to(group.name, - url('repos_group_home', group_name=group.group_name)) - return literal(' » '.join(map(make_link, groups) + ['%s' % last_link])) - - def fancy_file_stats(stats): """ Displays a fancy two colored bar for number of added/deleted @@ -1241,151 +964,186 @@ return literal('
%s%s
' % (width, d_a, d_d)) -def _urlify_text(s): - """ - Extract urls from text and make html links out of them +_URLIFY_RE = re.compile(r''' +# URL markup +(?P%s) | +# @mention markup +(?P%s) | +# Changeset hash markup +(?[0-9a-f]{12,40}) +(?!\w|[-_]) | +# Markup of *bold text* +(?: + (?:^|(?<=\s)) + (?P [*] (?!\s) [^*\n]* (?[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] | +\[license\ \=>\ *(?P[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] | +\[(?Prequires|recommends|conflicts|base)\ \=>\ *(?P[a-zA-Z0-9\-\/]*)\] | +\[(?:lang|language)\ \=>\ *(?P[a-zA-Z\-\/\#\+]*)\] | +\[(?P[a-z]+)\] +''' % (url_re.pattern, MENTIONS_REGEX.pattern), + re.VERBOSE | re.MULTILINE | re.IGNORECASE) + + + +def urlify_text(s, repo_name=None, link_=None, truncate=None, stylize=False, truncatef=truncate): """ - def url_func(match_obj): - url_full = match_obj.group(1) - return '%(url)s' % ({'url': url_full}) - return url_re.sub(url_func, s) + Parses given text message and make literal html with markup. + The text will be truncated to the specified length. + Hashes are turned into changeset links to specified repository. + URLs links to what they say. + Issues are linked to given issue-server. + If link_ is provided, all text not already linking somewhere will link there. + """ -def urlify_text(s, truncate=None, stylize=False, truncatef=truncate): - """ - Extract urls from text and make literal html links out of them - """ - if truncate is not None: - s = truncatef(s, truncate) + def _replace(match_obj): + url = match_obj.group('url') + if url is not None: + return '%(url)s' % {'url': url} + mention = match_obj.group('mention') + if mention is not None: + return '%s' % mention + hash_ = match_obj.group('hash') + if hash_ is not None and repo_name is not None: + from kallithea.config.routing import url # doh, we need to re-import url to mock it later + return '%(hash)s' % { + 'url': url('changeset_home', repo_name=repo_name, revision=hash_), + 'hash': hash_, + } + bold = match_obj.group('bold') + if bold is not None: + return '*%s*' % _urlify(bold[1:-1]) + if stylize: + seen = match_obj.group('seen') + if seen: + return '
see => %s
' % seen + license = match_obj.group('license') + if license: + return '' % (license, license) + tagtype = match_obj.group('tagtype') + if tagtype: + tagvalue = match_obj.group('tagvalue') + return '
%s => %s
' % (tagtype, tagtype, tagvalue, tagvalue) + lang = match_obj.group('lang') + if lang: + return '
%s
' % lang + tag = match_obj.group('tag') + if tag: + return '
%s
' % (tag, tag) + return match_obj.group(0) + + def _urlify(s): + """ + Extract urls from text and make html links out of them + """ + return _URLIFY_RE.sub(_replace, s) + + if truncate is None: + s = s.rstrip() + else: + s = truncatef(s, truncate, whole_word=True) s = html_escape(s) - if stylize: - s = desc_stylize(s) - s = _urlify_text(s) + s = _urlify(s) + if repo_name is not None: + s = urlify_issues(s, repo_name) + if link_ is not None: + # make href around everything that isn't a href already + s = linkify_others(s, link_) + s = s.replace('\r\n', '
').replace('\n', '
') return literal(s) -def urlify_changesets(text_, repository): - """ - Extract revision ids from changeset and make link from them - - :param text_: - :param repository: repo name to build the URL with - """ - from pylons import url # doh, we need to re-import url to mock it later - - def url_func(match_obj): - rev = match_obj.group(0) - return '%(rev)s' % { - 'url': url('changeset_home', repo_name=repository, revision=rev), - 'rev': rev, - } - - return re.sub(r'(?:^|(?<=[\s(),]))([0-9a-fA-F]{12,40})(?=$|\s|[.,:()])', url_func, text_) def linkify_others(t, l): + """Add a default link to html with links. + HTML doesn't allow nesting of links, so the outer link must be broken up + in pieces and give space for other links. + """ urls = re.compile(r'(\)',) links = [] for e in urls.split(t): - if not urls.match(e): + if e.strip() and not urls.match(e): links.append('%s' % (l, e)) else: links.append(e) return ''.join(links) -def urlify_commit(text_, repository, link_=None): - """ - Parses given text message and makes proper links. - issues are linked to given issue-server, and rest is a changeset link - if link_ is given, in other case it's a plain text - :param text_: - :param repository: - :param link_: changeset link - """ - newtext = html_escape(text_) - - # urlify changesets - extract revisions and make link out of them - newtext = urlify_changesets(newtext, repository) - - # extract http/https links and make them real urls - newtext = _urlify_text(newtext) - - newtext = urlify_issues(newtext, repository, link_) - - return literal(newtext) - -def urlify_issues(newtext, repository, link_=None): - from kallithea import CONFIG as conf - - # allow multiple issue servers to be used - valid_indices = [ - x.group(1) - for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys()) - if x and 'issue_server_link%s' % x.group(1) in conf - and 'issue_prefix%s' % x.group(1) in conf - ] - - if valid_indices: - log.debug('found issue server suffixes `%s` during valuation of: %s', - ','.join(valid_indices), newtext) - - for pattern_index in valid_indices: - ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index) - ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index) - ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index) - - log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s', - pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK, - ISSUE_PREFIX) - - URL_PAT = re.compile(ISSUE_PATTERN) - - def url_func(match_obj): - pref = '' - if match_obj.group().startswith(' '): - pref = ' ' - - issue_id = ''.join(match_obj.groups()) - issue_url = ISSUE_SERVER_LNK.replace('{id}', issue_id) - if repository: - issue_url = issue_url.replace('{repo}', repository) - repo_name = repository.split(URL_SEP)[-1] - issue_url = issue_url.replace('{repo_name}', repo_name) - - return ( - '%(pref)s' - '%(issue-prefix)s%(id-repr)s' - '' - ) % { - 'pref': pref, - 'cls': 'issue-tracker-link', - 'url': issue_url, - 'id-repr': issue_id, - 'issue-prefix': ISSUE_PREFIX, - 'serv': ISSUE_SERVER_LNK, - } - newtext = URL_PAT.sub(url_func, newtext) - log.debug('processed prefix:`%s` => %s', pattern_index, newtext) - - # if we actually did something above - if link_: - # wrap not links into final link => link_ - newtext = linkify_others(newtext, link_) - return newtext +# Global variable that will hold the actual urlify_issues function body. +# Will be set on first use when the global configuration has been read. +_urlify_issues_f = None -def rst(source): - return literal('
%s
' % - MarkupRenderer.rst(source)) +def urlify_issues(newtext, repo_name): + """Urlify issue references according to .ini configuration""" + global _urlify_issues_f + if _urlify_issues_f is None: + from kallithea import CONFIG + from kallithea.model.db import URL_SEP + assert CONFIG['sqlalchemy.url'] # make sure config has been loaded + + # Build chain of urlify functions, starting with not doing any transformation + tmp_urlify_issues_f = lambda s: s + + issue_pat_re = re.compile(r'issue_pat(.*)') + for k in CONFIG.keys(): + # Find all issue_pat* settings that also have corresponding server_link and prefix configuration + m = issue_pat_re.match(k) + if m is None: + continue + suffix = m.group(1) + issue_pat = CONFIG.get(k) + issue_server_link = CONFIG.get('issue_server_link%s' % suffix) + issue_prefix = CONFIG.get('issue_prefix%s' % suffix) + if issue_pat and issue_server_link and issue_prefix: + log.debug('issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_prefix) + else: + log.error('skipping incomplete issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_prefix) + continue + + # Wrap tmp_urlify_issues_f with substitution of this pattern, while making sure all loop variables (and compiled regexpes) are bound + issue_re = re.compile(issue_pat) + def issues_replace(match_obj, + issue_server_link=issue_server_link, issue_prefix=issue_prefix): + leadingspace = ' ' if match_obj.group().startswith(' ') else '' + issue_id = ''.join(match_obj.groups()) + issue_url = issue_server_link.replace('{id}', issue_id) + issue_url = issue_url.replace('{repo}', repo_name) + issue_url = issue_url.replace('{repo_name}', repo_name.split(URL_SEP)[-1]) + return ( + '%(leadingspace)s' + '%(issue-prefix)s%(id-repr)s' + '' + ) % { + 'leadingspace': leadingspace, + 'url': issue_url, + 'id-repr': issue_id, + 'issue-prefix': issue_prefix, + 'serv': issue_server_link, + } + tmp_urlify_issues_f = (lambda s, + issue_re=issue_re, issues_replace=issues_replace, chain_f=tmp_urlify_issues_f: + issue_re.sub(issues_replace, chain_f(s))) + + # Set tmp function globally - atomically + _urlify_issues_f = tmp_urlify_issues_f + + return _urlify_issues_f(newtext) -def rst_w_mentions(source): +def render_w_mentions(source, repo_name=None): """ - Wrapped rst renderer with @mention highlighting + Render plain text with revision hashes and issue references urlified + and with @mention highlighting. + """ + s = safe_unicode(source) + s = urlify_text(s, repo_name=repo_name) + return literal('
%s
' % s) - :param source: - """ - return literal('
%s
' % - MarkupRenderer.rst_with_mentions(source)) def short_ref(ref_type, ref_name): if ref_type == 'rev': @@ -1410,14 +1168,17 @@ return l def changeset_status(repo, revision): + from kallithea.model.changeset_status import ChangesetStatusModel return ChangesetStatusModel().get_status(repo, revision) def changeset_status_lbl(changeset_status): - return dict(ChangesetStatus.STATUSES).get(changeset_status) + from kallithea.model.db import ChangesetStatus + return ChangesetStatus.get_status_lbl(changeset_status) def get_permission_name(key): + from kallithea.model.db import Permission return dict(Permission.PERMS).get(key) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/hooks.py --- a/kallithea/lib/hooks.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/hooks.py Sat Dec 24 00:34:38 2016 +0100 @@ -35,7 +35,7 @@ from kallithea.lib.utils import action_logger from kallithea.lib.vcs.backends.base import EmptyChangeset from kallithea.lib.exceptions import HTTPLockedRC, UserCreationError -from kallithea.lib.utils2 import safe_str, _extract_extras +from kallithea.lib.utils2 import safe_str, safe_unicode, _extract_extras from kallithea.model.db import Repository, User @@ -79,12 +79,11 @@ last_cs = repo[len(repo) - 1] - msg = ('Repository size .hg:%s repo:%s total:%s\n' + msg = ('Repository size .hg: %s Checkout: %s Total: %s\n' 'Last revision is now r%s:%s\n') % ( size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12] ) - - sys.stdout.write(msg) + ui.status(msg) def pre_push(ui, repo, **kwargs): @@ -100,14 +99,13 @@ _http_ret = HTTPLockedRC(ex.repository, locked_by) if str(_http_ret.code).startswith('2'): #2xx Codes don't raise exceptions - sys.stdout.write(_http_ret.title) + ui.status(safe_str(_http_ret.title)) else: raise _http_ret def pre_pull(ui, repo, **kwargs): - # pre push function, currently used to ban pushing when - # repository is locked + # pre pull function ... ex = _extract_extras() if ex.locked_by[0]: locked_by = User.get(ex.locked_by[0]).username @@ -116,7 +114,7 @@ _http_ret = HTTPLockedRC(ex.repository, locked_by) if str(_http_ret.code).startswith('2'): #2xx Codes don't raise exceptions - sys.stdout.write(_http_ret.title) + ui.status(safe_str(_http_ret.title)) else: raise _http_ret @@ -144,14 +142,14 @@ if ex.make_lock is not None and ex.make_lock: Repository.lock(Repository.get_by_repo_name(ex.repository), user.user_id) #msg = 'Made lock on repo `%s`' % repository - #sys.stdout.write(msg) + #ui.status(msg) if ex.locked_by[0]: locked_by = User.get(ex.locked_by[0]).username _http_ret = HTTPLockedRC(ex.repository, locked_by) if str(_http_ret.code).startswith('2'): #2xx Codes don't raise exceptions - sys.stdout.write(_http_ret.title) + ui.status(safe_str(_http_ret.title)) return 0 @@ -199,15 +197,14 @@ if ex.make_lock is not None and not ex.make_lock: Repository.unlock(Repository.get_by_repo_name(ex.repository)) - msg = 'Released lock on repo `%s`\n' % ex.repository - sys.stdout.write(msg) + ui.status(safe_str('Released lock on repo `%s`\n' % ex.repository)) if ex.locked_by[0]: locked_by = User.get(ex.locked_by[0]).username _http_ret = HTTPLockedRC(ex.repository, locked_by) if str(_http_ret.code).startswith('2'): #2xx Codes don't raise exceptions - sys.stdout.write(_http_ret.title) + ui.status(safe_str(_http_ret.title)) return 0 @@ -227,7 +224,7 @@ 'created_on', 'enable_downloads', 'repo_id', - 'user_id', + 'owner_id', 'enable_statistics', 'clone_uri', 'fork_id', @@ -309,7 +306,7 @@ 'created_on', 'enable_downloads', 'repo_id', - 'user_id', + 'owner_id', 'enable_statistics', 'clone_uri', 'fork_id', @@ -376,8 +373,8 @@ def handle_git_receive(repo_path, revs, env, hook_type): """ A really hacky method that is run 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 environment, + a push action together with pushed revisions. It's executed by subprocess + thus needs all info to be able to create an on the fly app environment, connect to database and run the logging code. Hacky as sh*t but works. :param repo_path: @@ -387,17 +384,18 @@ from paste.deploy import appconfig from sqlalchemy import engine_from_config from kallithea.config.environment import load_environment - from kallithea.model import init_model + from kallithea.model.base import init_model from kallithea.model.db import Ui from kallithea.lib.utils import make_ui extras = _extract_extras(env) + repo_path = safe_unicode(repo_path) path, ini_name = os.path.split(extras['config']) conf = appconfig('config:%s' % ini_name, relative_to=path) load_environment(conf.global_conf, conf.local_conf, test_env=False, test_index=False) - engine = engine_from_config(conf, 'sqlalchemy.db1.') + engine = engine_from_config(conf, 'sqlalchemy.') init_model(engine) baseui = make_ui('db') @@ -425,14 +423,14 @@ elif hook_type == 'post' and _hooks.get(Ui.HOOK_PUSH): rev_data = [] for l in revs: - old_rev, new_rev, ref = l.split(' ') + old_rev, new_rev, ref = l.strip().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()}) + 'name': '/'.join(_ref_data[2:])}) git_revs = [] diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/indexers/__init__.py --- a/kallithea/lib/indexers/__init__.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/indexers/__init__.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,10 +28,10 @@ import os import sys import logging -from os.path import dirname as dn, join as jn +from os.path import dirname # Add location of top level folder to sys.path -sys.path.append(dn(dn(dn(os.path.realpath(__file__))))) +sys.path.append(dirname(dirname(dirname(os.path.realpath(__file__))))) from whoosh.analysis import RegexTokenizer, LowercaseFilter from whoosh.fields import TEXT, ID, STORED, NUMERIC, BOOLEAN, Schema, FieldType, DATETIME @@ -140,7 +140,7 @@ res = self.searcher.stored_fields(docid[0]) log.debug('result: %s', res) if self.search_type == 'content': - full_repo_path = jn(self.repo_location, res['repository']) + full_repo_path = os.path.join(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]) @@ -149,7 +149,7 @@ 'f_path': f_path }) elif self.search_type == 'path': - full_repo_path = jn(self.repo_location, res['repository']) + full_repo_path = os.path.join(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}) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/indexers/daemon.py --- a/kallithea/lib/indexers/daemon.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/indexers/daemon.py Sat Dec 24 00:34:38 2016 +0100 @@ -34,14 +34,13 @@ from shutil import rmtree from time import mktime -from os.path import dirname as dn -from os.path import join as jn +from os.path import dirname # Add location of top level folder to sys.path -project_path = dn(dn(dn(dn(os.path.realpath(__file__))))) +project_path = dirname(dirname(dirname(dirname(os.path.realpath(__file__))))) sys.path.append(project_path) -from kallithea.config.conf import INDEX_EXTENSIONS +from kallithea.config.conf import INDEX_EXTENSIONS, INDEX_FILENAMES from kallithea.model.scm import ScmModel from kallithea.model.db import Repository from kallithea.lib.utils2 import safe_unicode, safe_str @@ -136,7 +135,7 @@ cs = self._get_index_changeset(repo) for _topnode, _dirs, files in cs.walk('/'): for f in files: - index_paths_.add(jn(safe_str(repo.path), safe_str(f.path))) + index_paths_.add(os.path.join(safe_str(repo.path), safe_str(f.path))) except RepositoryError: log.debug(traceback.format_exc()) @@ -162,6 +161,13 @@ node = cs.get_node(node_path) return node + def is_indexable_node(self, node): + """ + Just index the content of chosen files, skipping binary files + """ + return (node.extension in INDEX_EXTENSIONS or node.name in INDEX_FILENAMES) and \ + not node.is_binary + def get_node_mtime(self, node): return mktime(node.last_changeset.date.timetuple()) @@ -177,8 +183,7 @@ return 0, 0 indexed = indexed_w_content = 0 - # we just index the content of chosen files, and skip binary files - if node.extension in INDEX_EXTENSIONS and not node.is_binary: + if self.is_indexable_node(node): u_content = node.content if not isinstance(u_content, unicode): log.warning(' >> %s Could not get this content as unicode ' diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/ipaddr.py --- a/kallithea/lib/ipaddr.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/ipaddr.py Sat Dec 24 00:34:38 2016 +0100 @@ -1245,7 +1245,7 @@ '192.168.1.1' '192.168.1.1/255.255.255.255' '192.168.1.1/32' - are also functionaly equivalent. That is to say, failing to + are also functionally equivalent. That is to say, failing to provide a subnetmask will create an object with a mask of /32. If the mask (portion after the / in the argument) is given in diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/markup_renderer.py --- a/kallithea/lib/markup_renderer.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/markup_renderer.py Sat Dec 24 00:34:38 2016 +0100 @@ -35,8 +35,9 @@ log = logging.getLogger(__name__) -url_re = re.compile(r'''(\bhttps?://(?:[\da-zA-Z0-9@:.-]+)''' - r'''(?:[/a-zA-Z0-9_=@#~&+%.,:;?!*()-]*[/a-zA-Z0-9_=@#~])?)''') +url_re = re.compile(r'''\bhttps?://(?:[\da-zA-Z0-9@:.-]+)''' + r'''(?:[/a-zA-Z0-9_=@#~&+%.,:;?!*()-]*[/a-zA-Z0-9_=@#~])?''') + class MarkupRenderer(object): RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw'] @@ -132,7 +133,7 @@ source = newline.join(source.splitlines()) def url_func(match_obj): - url_full = match_obj.groups()[0] + url_full = match_obj.group(0) return '%(url)s' % ({'url': url_full}) source = url_re.sub(url_func, source) return '
' + source.replace("\n", '
') @@ -189,10 +190,9 @@ @classmethod def rst_with_mentions(cls, source): - mention_pat = re.compile(MENTIONS_REGEX) def wrapp(match_obj): uname = match_obj.groups()[0] return '\ **@%(uname)s**\ ' % {'uname': uname} - mention_hl = mention_pat.sub(wrapp, source).strip() + mention_hl = MENTIONS_REGEX.sub(wrapp, source).strip() return cls.rst(mention_hl) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/middleware/appenlight.py diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/middleware/pygrack.py --- a/kallithea/lib/middleware/pygrack.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/middleware/pygrack.py Sat Dec 24 00:34:38 2016 +0100 @@ -1,3 +1,30 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +kallithea.lib.middleware.pygrack +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Python implementation of git-http-backend's Smart HTTP protocol + +Based on original code from git_http_backend.py project. + +Copyright (c) 2010 Daniel Dotsenko +Copyright (c) 2012 Marcin Kuzminski + +This file was forked by the Kallithea project in July 2014. +""" + import os import socket import logging @@ -7,6 +34,7 @@ import kallithea from kallithea.lib.vcs import subprocessio +from kallithea.lib.utils2 import safe_unicode log = logging.getLogger(__name__) @@ -59,7 +87,9 @@ :param path: """ - return path.split(self.repo_name, 1)[-1].strip('/') + path = safe_unicode(path) + assert path.startswith('/' + self.repo_name + '/') + return path[len(self.repo_name) + 2:].strip('/') def inforefs(self, request, environ): """ @@ -72,14 +102,17 @@ 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 + # From Documentation/technical/http-protocol.txt shipped with Git: + # + # Clients MUST verify the first pkt-line is `# service=$servicename`. + # Servers MUST set $servicename to be the request parameter value. + # Servers SHOULD include an LF at the end of this line. + # Clients MUST ignore an LF at the end of the line. + # + # smart_reply = PKT-LINE("# service=$servicename" LF) + # ref_list + # "0000" + server_advert = '# service=%s\n' % git_command packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower() _git_path = kallithea.CONFIG.get('git_path', 'git') cmd = [_git_path, git_command[4:], diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/middleware/simplegit.py --- a/kallithea/lib/middleware/simplegit.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/middleware/simplegit.py Sat Dec 24 00:34:38 2016 +0100 @@ -38,7 +38,7 @@ HTTPNotAcceptable from kallithea.model.db import User, Ui -from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url,\ +from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url, \ _set_extras from kallithea.lib.base import BaseVCSController, WSGIResultCloseCallback from kallithea.lib.utils import make_ui, is_valid_repo @@ -66,8 +66,6 @@ def _handle_request(self, environ, start_response): if not is_git(environ): return self.application(environ, start_response) - if not self._check_ssl(environ): - return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) ip_addr = self._get_ip_addr(environ) username = None @@ -99,7 +97,7 @@ # CHECK ANONYMOUS PERMISSION #====================================================================== if action in ['pull', 'push']: - anonymous_user = self.__get_user('default') + anonymous_user = User.get_default_user(cache=True) username = anonymous_user.username if anonymous_user.active: # ONLY check permissions if the user is activated @@ -146,7 +144,7 @@ # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME #============================================================== try: - user = self.__get_user(username) + user = User.get_by_username_or_email(username) if user is None or not user.active: return HTTPForbidden()(environ, start_response) username = user.username @@ -210,8 +208,7 @@ lambda: self._invalidate_cache(repo_name)) return result except HTTPLockedRC as e: - _code = CONFIG.get('lock_ret_code') - log.debug('Repository LOCKED ret code %s!', _code) + log.debug('Locked, response %s: %s', e.code, e.title) return e(environ, start_response) except Exception: log.error(traceback.format_exc()) @@ -228,7 +225,7 @@ from kallithea.lib.middleware.pygrack import make_wsgi_app app = make_wsgi_app( repo_root=safe_str(self.basepath), - repo_name=repo_name, + repo_name=safe_unicode(repo_name), extras=extras, ) return app @@ -248,9 +245,6 @@ return repo_name - def __get_user(self, username): - return User.get_by_username(username) - def __get_action(self, environ): """ Maps git request commands into a pull or push command. @@ -295,12 +289,11 @@ if action == 'pull' and _hooks.get(Ui.HOOK_PULL): log_pull_action(ui=baseui, repo=_repo._repo) - def __inject_extras(self, repo_path, baseui, extras={}): + def __inject_extras(self, repo_path, baseui, extras=None): """ Injects some extra params into baseui instance :param baseui: baseui instance :param extras: dict with extra params to put into baseui """ - - _set_extras(extras) + _set_extras(extras or {}) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/middleware/simplehg.py --- a/kallithea/lib/middleware/simplehg.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/middleware/simplehg.py Sat Dec 24 00:34:38 2016 +0100 @@ -37,7 +37,7 @@ HTTPNotAcceptable from kallithea.model.db import User -from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url,\ +from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url, \ _set_extras from kallithea.lib.base import BaseVCSController, WSGIResultCloseCallback from kallithea.lib.utils import make_ui, is_valid_repo, ui_sections @@ -71,8 +71,6 @@ def _handle_request(self, environ, start_response): if not is_mercurial(environ): return self.application(environ, start_response) - if not self._check_ssl(environ): - return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) ip_addr = self._get_ip_addr(environ) username = None @@ -105,7 +103,7 @@ # CHECK ANONYMOUS PERMISSION #====================================================================== if action in ['pull', 'push']: - anonymous_user = self.__get_user('default') + anonymous_user = User.get_default_user(cache=True) username = anonymous_user.username if anonymous_user.active: # ONLY check permissions if the user is activated @@ -152,7 +150,7 @@ # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME #============================================================== try: - user = self.__get_user(username) + user = User.get_by_username_or_email(username) if user is None or not user.active: return HTTPForbidden()(environ, start_response) username = user.username @@ -217,8 +215,8 @@ if str(e).find('not found') != -1: return HTTPNotFound()(environ, start_response) except HTTPLockedRC as e: - _code = CONFIG.get('lock_ret_code') - log.debug('Repository LOCKED ret code %s!', _code) + # Before Mercurial 3.6, lock exceptions were caught here + log.debug('Locked, response %s: %s', e.code, e.title) return e(environ, start_response) except Exception: log.error(traceback.format_exc()) @@ -229,7 +227,18 @@ Make an wsgi application using hgweb, and inject generated baseui instance, additionally inject some extras into ui object """ - return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui) + class HgWebWrapper(hgweb_mod.hgweb): + # Work-around for Mercurial 3.6+ causing lock exceptions to be + # thrown late + def _runwsgi(self, req, repo): + try: + return super(HgWebWrapper, self)._runwsgi(req, repo) + except HTTPLockedRC as e: + log.debug('Locked, response %s: %s', e.code, e.title) + req.respond(e.status, 'text/plain') + return '' + + return HgWebWrapper(repo_name, name=repo_name, baseui=baseui) def __get_repository(self, environ): """ @@ -248,9 +257,6 @@ return repo_name - def __get_user(self, username): - return User.get_by_username(username) - def __get_action(self, environ): """ Maps mercurial request commands into a clone,pull or push command. @@ -275,7 +281,7 @@ raise Exception('Unable to detect pull/push action !!' 'Are you using non standard command or client ?') - def __inject_extras(self, repo_path, baseui, extras={}): + def __inject_extras(self, repo_path, baseui, extras=None): """ Injects some extra params into baseui instance @@ -294,4 +300,4 @@ for section in ui_sections: for k, v in repoui.configitems(section): baseui.setconfig(section, k, v) - _set_extras(extras) + _set_extras(extras or {}) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/page.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/lib/page.py Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Custom paging classes +""" + +import math +import re +from kallithea.config.routing import url +from webhelpers.html import literal, HTML +from webhelpers.paginate import Page as _Page + +class Page(_Page): + """ + Custom pager to match rendering style with YUI paginator + """ + + def __init__(self, *args, **kwargs): + kwargs.setdefault('url', url.current) + _Page.__init__(self, *args, **kwargs) + + def _get_pos(self, cur_page, max_page, items): + edge = (items / 2) + 1 + if (cur_page <= edge): + radius = max(items / 2, items - cur_page) + elif (max_page - cur_page) < edge: + radius = (items - 1) - (max_page - cur_page) + else: + radius = items / 2 + + left = max(1, (cur_page - (radius))) + right = min(max_page, cur_page + (radius)) + return left, cur_page, right + + def _range(self, regexp_match): + """ + Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8'). + + Arguments: + + regexp_match + A "re" (regular expressions) match object containing the + radius of linked pages around the current page in + regexp_match.group(1) as a string + + This function is supposed to be called as a callable in + re.sub. + + """ + radius = int(regexp_match.group(1)) + + # Compute the first and last page number within the radius + # e.g. '1 .. 5 6 [7] 8 9 .. 12' + # -> leftmost_page = 5 + # -> rightmost_page = 9 + leftmost_page, _cur, rightmost_page = self._get_pos(self.page, + self.last_page, + (radius * 2) + 1) + nav_items = [] + + # Create a link to the first page (unless we are on the first page + # or there would be no need to insert '..' spacers) + if self.page != self.first_page and self.first_page < leftmost_page: + nav_items.append(HTML.li(self._pagerlink(self.first_page, self.first_page))) + + # Insert dots if there are pages between the first page + # and the currently displayed page range + if leftmost_page - self.first_page > 1: + # Wrap in a SPAN tag if nolink_attr is set + text_ = '..' + if self.dotdot_attr: + text_ = HTML.span(c=text_, **self.dotdot_attr) + nav_items.append(HTML.li(text_)) + + for thispage in xrange(leftmost_page, rightmost_page + 1): + # Highlight the current page number and do not use a link + text_ = str(thispage) + if thispage == self.page: + # Wrap in a SPAN tag if nolink_attr is set + if self.curpage_attr: + text_ = HTML.li(HTML.span(c=text_), **self.curpage_attr) + nav_items.append(text_) + # Otherwise create just a link to that page + else: + nav_items.append(HTML.li(self._pagerlink(thispage, text_))) + + # Insert dots if there are pages between the displayed + # page numbers and the end of the page range + if self.last_page - rightmost_page > 1: + text_ = '..' + # Wrap in a SPAN tag if nolink_attr is set + if self.dotdot_attr: + text_ = HTML.span(c=text_, **self.dotdot_attr) + nav_items.append(HTML.li(text_)) + + # Create a link to the very last page (unless we are on the last + # page or there would be no need to insert '..' spacers) + if self.page != self.last_page and rightmost_page < self.last_page: + nav_items.append(HTML.li(self._pagerlink(self.last_page, self.last_page))) + + #_page_link = url.current() + #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) + #nav_items.append(literal('' % (_page_link, str(int(self.page)+1)))) + return self.separator.join(nav_items) + + def pager(self, format='$link_previous ~2~ $link_next', page_param='page', partial_param='partial', + show_if_single_page=False, separator=' ', onclick=None, + symbol_first='<<', symbol_last='>>', + symbol_previous='<', symbol_next='>', + link_attr=None, + curpage_attr=None, + dotdot_attr=None, **kwargs): + self.curpage_attr = curpage_attr or {'class': 'active'} + self.separator = separator + self.pager_kwargs = kwargs + self.page_param = page_param + self.partial_param = partial_param + self.onclick = onclick + self.link_attr = link_attr or {'class': 'pager_link', 'rel': 'prerender'} + self.dotdot_attr = dotdot_attr or {'class': 'pager_dotdot'} + + # Don't show navigator if there is no more than one page + if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): + return '' + + from string import Template + # Replace ~...~ in token format by range of pages + result = re.sub(r'~(\d+)~', self._range, format) + + # Interpolate '%' variables + result = Template(result).safe_substitute({ + 'first_page': self.first_page, + 'last_page': self.last_page, + 'page': self.page, + 'page_count': self.page_count, + 'items_per_page': self.items_per_page, + 'first_item': self.first_item, + 'last_item': self.last_item, + 'item_count': self.item_count, + 'link_first': self.page > self.first_page and \ + self._pagerlink(self.first_page, symbol_first) or '', + 'link_last': self.page < self.last_page and \ + self._pagerlink(self.last_page, symbol_last) or '', + 'link_previous': HTML.li(self.previous_page and \ + self._pagerlink(self.previous_page, symbol_previous) \ + or HTML.a(symbol_previous)), + 'link_next': HTML.li(self.next_page and \ + self._pagerlink(self.next_page, symbol_next) \ + or HTML.a(symbol_next)) + }) + + return literal(result) + + +class RepoPage(Page): + + def __init__(self, collection, page=1, items_per_page=20, + item_count=None, **kwargs): + + """Create a "RepoPage" instance. special pager for paging + repository + """ + # TODO: call baseclass __init__ + self._url_generator = kwargs.pop('url', url.current) + + # Safe the kwargs class-wide so they can be used in the pager() method + self.kwargs = kwargs + + # Save a reference to the collection + self.original_collection = collection + + self.collection = collection + + # The self.page is the number of the current page. + # The first page has the number 1! + try: + self.page = int(page) # make it int() if we get it as a string + except (ValueError, TypeError): + self.page = 1 + + self.items_per_page = items_per_page + + # Unless the user tells us how many items the collections has + # we calculate that ourselves. + if item_count is not None: + self.item_count = item_count + else: + self.item_count = len(self.collection) + + # Compute the number of the first and last available page + if self.item_count > 0: + self.first_page = 1 + self.page_count = int(math.ceil(float(self.item_count) / + self.items_per_page)) + self.last_page = self.first_page + self.page_count - 1 + + # Make sure that the requested page number is the range of + # valid pages + if self.page > self.last_page: + self.page = self.last_page + elif self.page < self.first_page: + self.page = self.first_page + + # Note: the number of items on this page can be less than + # items_per_page if the last page is not full + self.first_item = max(0, (self.item_count) - (self.page * + items_per_page)) + self.last_item = ((self.item_count - 1) - items_per_page * + (self.page - 1)) + + self.items = list(self.collection[self.first_item:self.last_item + 1]) + + # Links to previous and next page + if self.page > self.first_page: + self.previous_page = self.page - 1 + else: + self.previous_page = None + + if self.page < self.last_page: + self.next_page = self.page + 1 + else: + self.next_page = None + + # No items available + else: + self.first_page = None + self.page_count = 0 + self.last_page = None + self.first_item = None + self.last_item = None + self.previous_page = None + self.next_page = None + self.items = [] + + # This is a subclass of the 'list' type. Initialise the list now. + list.__init__(self, reversed(self.items)) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/cache_keys.py --- a/kallithea/lib/paster_commands/cache_keys.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/cache_keys.py Sat Dec 24 00:34:38 2016 +0100 @@ -29,19 +29,12 @@ import os import sys -import logging +from kallithea.lib.paster_commands.common import BasePasterCommand from kallithea.model.meta import Session -from kallithea.lib.utils import BasePasterCommand +from kallithea.lib.utils2 import safe_str from kallithea.model.db import CacheInvalidation -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) - -log = logging.getLogger(__name__) - class Command(BasePasterCommand): @@ -57,18 +50,18 @@ def command(self): #get SqlAlchemy session self._init_session() + _caches = CacheInvalidation.query().order_by(CacheInvalidation.cache_key).all() if self.options.show: for c_obj in _caches: - print 'key:%s active:%s' % (c_obj.cache_key, c_obj.cache_active) + print 'key:%s active:%s' % (safe_str(c_obj.cache_key), c_obj.cache_active) elif self.options.cleanup: for c_obj in _caches: Session().delete(c_obj) - print 'removing key:%s' % (c_obj.cache_key) - Session().commit() + print 'Removing key: %s' % (safe_str(c_obj.cache_key)) + Session().commit() else: - print 'nothing done exiting...' - sys.exit(0) + print 'Nothing done, exiting...' def update_parser(self): self.parser.add_option( diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/celeryd.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/lib/paster_commands/celeryd.py Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +import kallithea +from kallithea.lib.paster_commands.common import BasePasterCommand +from kallithea.lib.utils import load_rcextensions +from kallithea.lib.utils2 import str2bool + +__all__ = ['Command'] + + +class Command(BasePasterCommand): + """Start the celery worker + + Starts the celery worker that uses a paste.deploy configuration + file. + """ + + usage = 'CONFIG_FILE [celeryd options...]' + summary = __doc__.splitlines()[0] + description = "".join(__doc__.splitlines()[2:]) + group_name = "Kallithea" + + parser = BasePasterCommand.standard_parser(quiet=True) + + def update_parser(self): + from kallithea.lib import celerypylons + cmd = celerypylons.worker.worker(celerypylons.app.app_or_default()) + for x in cmd.get_options(): + self.parser.add_option(x) + + def command(self): + from kallithea.lib import celerypylons + from pylons import config + try: + CELERY_ON = str2bool(config['app_conf'].get('use_celery')) + except KeyError: + CELERY_ON = False + + if not CELERY_ON: + raise Exception('Please set use_celery = true in .ini config ' + 'file before running celeryd') + kallithea.CELERY_ON = CELERY_ON + + load_rcextensions(config['here']) + cmd = celerypylons.worker.worker(celerypylons.app.app_or_default()) + return cmd.run(**vars(self.options)) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/cleanup.py --- a/kallithea/lib/paster_commands/cleanup.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/cleanup.py Sat Dec 24 00:34:38 2016 +0100 @@ -22,7 +22,7 @@ Original author and date, and relevant copyright and licensing information is below: :created_on: Jul 14, 2012 :author: marcink -:copyright: (c) 2013 RhodeCode GmbH. +:copyright: (c) 2013 RhodeCode GmbH, and others. :license: GPLv3, see LICENSE.md for more details. """ @@ -31,20 +31,13 @@ import sys import re import shutil -import logging import datetime -from kallithea.lib.utils import BasePasterCommand, ask_ok, REMOVED_REPO_PAT +from kallithea.lib.paster_commands.common import ask_ok, BasePasterCommand +from kallithea.lib.utils import REMOVED_REPO_PAT from kallithea.lib.utils2 import safe_str from kallithea.model.db import Ui -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) - -log = logging.getLogger(__name__) - class Command(BasePasterCommand): @@ -76,7 +69,7 @@ :param name: """ - date_part = name[4:19] # 4:19 since we don't parse milisecods + date_part = name[4:19] # 4:19 since we don't parse milliseconds return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S') def command(self): @@ -97,6 +90,8 @@ self._extract_date(loc)]) else: dirs.append(loc) + if dirs: + print 'Scanning: %s' % dn_ #filter older than (if present)! now = datetime.datetime.now() @@ -110,27 +105,25 @@ to_remove_filtered.append([name, date_]) to_remove = to_remove_filtered - print >> sys.stdout, 'removing %s deleted repos older than %s (%s)' \ + print '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) + print '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('the following repositories will be deleted completely:\n%s\n' 'are you sure you want to remove them [y/n]?' - % ', \n'.join(['%s removed on %s' - % (safe_str(x[0]), safe_str(x[1])) for x in to_remove])) + % '\n'.join(['%s removed on %s' % (safe_str(x[0]), safe_str(x[1])) + for x in to_remove])) if remove: for path, date_ in to_remove: - print >> sys.stdout, 'removing repository %s' % path + print 'Removing repository %s' % path shutil.rmtree(path) else: - print 'nothing done exiting...' - sys.exit(0) + print 'Nothing done, exiting...' def update_parser(self): self.parser.add_option( diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/common.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/lib/paster_commands/common.py Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +kallithea.lib.paster_commands.common +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Common code for Paster commands. + +This file was forked by the Kallithea project in July 2014. +Original author and date, and relevant copyright and licensing information is below: +:created_on: Apr 18, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH, and others. +:license: GPLv3, see LICENSE.md for more details. +""" + +import os +import logging + +import paste +from paste.script.command import Command, BadCommand + +from kallithea.lib.utils import setup_cache_regions + + +def ask_ok(prompt, retries=4, complaint='Yes or no please!'): + while True: + ok = raw_input(prompt) + if ok in ('y', 'ye', 'yes'): + return True + if ok in ('n', 'no', 'nop', 'nope'): + return False + retries = retries - 1 + if retries < 0: + raise IOError + print complaint + + +class BasePasterCommand(Command): + """ + Abstract Base Class for paster commands. + """ + min_args = 1 + min_args_error = "Please provide a paster config file as an argument." + takes_config_file = 1 + requires_config_file = True + + def run(self, args): + """ + Overrides Command.run + + Checks for a config file argument and loads it. + """ + if len(args) < self.min_args: + raise BadCommand( + self.min_args_error % {'min_args': self.min_args, + 'actual_args': len(args)}) + + # Decrement because we're going to lob off the first argument. + # @@ This is hacky + self.min_args -= 1 + self.bootstrap_config(args[0]) + self.update_parser() + return super(BasePasterCommand, self).run(args[1:]) + + def update_parser(self): + """ + Abstract method. Allows for the class's parser to be updated + before the superclass's `run` method is called. Necessary to + allow options/arguments to be passed through to the underlying + celery command. + """ + raise NotImplementedError("Abstract Method.") + + def bootstrap_config(self, conf): + """ + Loads the app configuration. + """ + from pylons import config as pylonsconfig + + self.path_to_ini_file = os.path.realpath(conf) + conf = paste.deploy.appconfig('config:' + self.path_to_ini_file) + pylonsconfig.init_app(conf.global_conf, conf.local_conf) + + def _init_session(self): + """ + Inits SqlAlchemy Session + """ + logging.config.fileConfig(self.path_to_ini_file) + + from pylons import config + from kallithea.model.base import init_model + from kallithea.lib.utils2 import engine_from_config + setup_cache_regions(config) + engine = engine_from_config(config, 'sqlalchemy.') + init_model(engine) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/install_iis.py --- a/kallithea/lib/paster_commands/install_iis.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/install_iis.py Sat Dec 24 00:34:38 2016 +0100 @@ -1,12 +1,29 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +kallithea.lib.paster_commands.install_iis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +IIS installation tools for Kallithea +""" + + import os import sys from paste.script.appinstall import AbstractInstallCommand from paste.script.command import BadCommand -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) class Command(AbstractInstallCommand): default_verbosity = 1 @@ -37,7 +54,9 @@ except ImportError: raise BadCommand('missing requirement: isapi-wsgi not installed') - file = '''import sys + file = '''\ +# Created by Kallithea install_iis +import sys if hasattr(sys, "isapidllhandle"): import win32traceutil @@ -78,8 +97,8 @@ dispatchfile = os.path.join(os.getcwd(), 'dispatch.py') self.ensure_file(dispatchfile, outdata, False) - print 'generating', dispatchfile + print 'Generating %s' % (dispatchfile,) - print ('run \'python "%s" install\' with administrative privileges ' + print ('Run \'python "%s" install\' with administrative privileges ' 'to generate the _dispatch.dll file and install it into the ' 'default web site') % (dispatchfile,) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/ishell.py --- a/kallithea/lib/paster_commands/ishell.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/ishell.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,16 +28,14 @@ import os import sys -import logging - -from kallithea.lib.utils import BasePasterCommand -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) +# imports, used in IPython shell +import time +import shutil +import datetime +from kallithea.model.db import * -log = logging.getLogger(__name__) +from kallithea.lib.paster_commands.common import BasePasterCommand class Command(BasePasterCommand): @@ -55,14 +53,6 @@ #get SqlAlchemy session self._init_session() - # imports, used in ipython shell - import os - import sys - import time - import shutil - import datetime - from kallithea.model.db import * - try: from IPython import embed from IPython.config.loader import Config @@ -70,7 +60,7 @@ cfg.InteractiveShellEmbed.confirm_exit = False embed(config=cfg, banner1="Kallithea IShell.") except ImportError: - print 'ipython installation required for ishell' + print 'Kallithea ishell requires the IPython Python package' sys.exit(-1) def update_parser(self): diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/make_index.py --- a/kallithea/lib/paster_commands/make_index.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/make_index.py Sat Dec 24 00:34:38 2016 +0100 @@ -23,22 +23,17 @@ :author: marcink :copyright: (c) 2013 RhodeCode GmbH, and others. :license: GPLv3, see LICENSE.md for more details. - """ import os import sys -import logging +from os.path import dirname from string import strip from kallithea.model.repo import RepoModel -from kallithea.lib.utils import BasePasterCommand, load_rcextensions - -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) +from kallithea.lib.paster_commands.common import BasePasterCommand +from kallithea.lib.utils import load_rcextensions class Command(BasePasterCommand): @@ -53,7 +48,6 @@ summary = "Creates or updates full text search index" def command(self): - logging.config.fileConfig(self.path_to_ini_file) #get SqlAlchemy session self._init_session() from pylons import config @@ -74,12 +68,12 @@ from kallithea.lib.pidlock import LockHeld, DaemonLock from kallithea.lib.indexers.daemon import WhooshIndexingDaemon try: - l = DaemonLock(file_=os.path.join(dn(dn(index_location)), + l = DaemonLock(file_=os.path.join(dirname(dirname(index_location)), 'make_index.lock')) WhooshIndexingDaemon(index_location=index_location, repo_location=repo_location, repo_list=repo_list, - repo_update_list=repo_update_list)\ + repo_update_list=repo_update_list) \ .run(full_index=self.options.full_index) l.release() except LockHeld: diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/make_rcextensions.py --- a/kallithea/lib/paster_commands/make_rcextensions.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/make_rcextensions.py Sat Dec 24 00:34:38 2016 +0100 @@ -23,7 +23,6 @@ :author: marcink :copyright: (c) 2013 RhodeCode GmbH, and others. :license: GPLv3, see LICENSE.md for more details. - """ @@ -31,12 +30,7 @@ import sys import pkg_resources -from kallithea.lib.utils import BasePasterCommand, ask_ok - -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) +from kallithea.lib.paster_commands.common import ask_ok, BasePasterCommand class Command(BasePasterCommand): @@ -67,7 +61,7 @@ msg = ('Extension file already exists, do you want ' 'to overwrite it ? [y/n]') if not ask_ok(msg): - print 'Nothing done...' + print 'Nothing done, exiting...' return dirname = os.path.dirname(ext_file) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/repo_scan.py --- a/kallithea/lib/paster_commands/repo_scan.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/repo_scan.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,17 +28,10 @@ import os import sys -import logging from kallithea.model.scm import ScmModel -from kallithea.lib.utils import BasePasterCommand, repo2db_mapper - -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) - -log = logging.getLogger(__name__) +from kallithea.lib.paster_commands.common import BasePasterCommand +from kallithea.lib.utils import repo2db_mapper class Command(BasePasterCommand): @@ -61,7 +54,12 @@ remove_obsolete=rm_obsolete) added = ', '.join(added) or '-' removed = ', '.join(removed) or '-' - print 'Scan completed added: %s removed: %s' % (added, removed) + print 'Scan completed.' + print 'Added: %s' % added + if rm_obsolete: + print 'Removed: %s' % removed + else: + print 'Missing: %s' % removed def update_parser(self): self.parser.add_option( diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/setup_db.py --- a/kallithea/lib/paster_commands/setup_db.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/setup_db.py Sat Dec 24 00:34:38 2016 +0100 @@ -1,14 +1,30 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +kallithea.lib.paster_commands.setup_db +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Databaset setup paster command for Kallithea +""" + + import os import sys from paste.script.appinstall import AbstractInstallCommand from paste.script.command import BadCommand from paste.deploy import appconfig -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) - class Command(AbstractInstallCommand): @@ -20,11 +36,10 @@ group_name = "Kallithea" description = """\ - - Setup Kallithea according to its configuration file. This is - the second part of a two-phase web application installation - process (the first phase is prepare-app). The setup process - consist of things like setting up databases, creating super user + Setup Kallithea according to its configuration file. This is + the second part of a two-phase web application installation + process (the first phase is prepare-app). The setup process + consist of things like setting up databases, creating super user """ parser = AbstractInstallCommand.standard_parser( @@ -74,6 +89,7 @@ dest='public_access', default=None, help='Disable public access on this installation ') + def command(self): config_spec = self.args[0] section = self.options.section_name @@ -100,7 +116,7 @@ dist = conf.context.distribution if dist is None: raise BadCommand( - "The section %r is not the application (probably a filter). " + "The section %r is not the application (probably a filter). " "You should add #section_name, where section_name is the " "section that configures your application" % plain_section) installer = self.get_installer(dist, ep_group, ep_name) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/paster_commands/update_repoinfo.py --- a/kallithea/lib/paster_commands/update_repoinfo.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/paster_commands/update_repoinfo.py Sat Dec 24 00:34:38 2016 +0100 @@ -28,21 +28,14 @@ import os import sys -import logging import string -from kallithea.lib.utils import BasePasterCommand +from kallithea.lib.paster_commands.common import BasePasterCommand +from kallithea.lib.utils2 import safe_unicode from kallithea.model.db import Repository from kallithea.model.repo import RepoModel from kallithea.model.meta import Session -# Add location of top level folder to sys.path -from os.path import dirname as dn -rc_path = dn(dn(dn(os.path.realpath(__file__)))) -sys.path.append(rc_path) - -log = logging.getLogger(__name__) - class Command(BasePasterCommand): @@ -59,22 +52,24 @@ #get SqlAlchemy session self._init_session() - repo_update_list = map(string.strip, - self.options.repo_update_list.split(',')) \ - if self.options.repo_update_list else None - if repo_update_list is not None: - repo_list = list(Repository.query()\ - .filter(Repository.repo_name.in_(repo_update_list))) + if self.options.repo_update_list is None: + repo_list = Repository.query().all() else: - repo_list = Repository.getAll() - RepoModel.update_repoinfo(repositories=repo_list) + repo_names = [safe_unicode(n.strip()) + for n in self.options.repo_update_list.split(',')] + repo_list = list(Repository.query() + .filter(Repository.repo_name.in_(repo_names))) + for repo in repo_list: + repo.update_changeset_cache() Session().commit() if self.options.invalidate_cache: for r in repo_list: r.set_invalidate() - print 'Updated cache for %s repositories' % (len(repo_list)) + print 'Updated repo info and invalidated cache for %s repositories' % (len(repo_list)) + else: + print 'Updated repo info for %s repositories' % (len(repo_list)) def update_parser(self): self.parser.add_option('--update-only', diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/pygmentsutils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/lib/pygmentsutils.py Sat Dec 24 00:34:38 2016 +0100 @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +kallithea.lib.pygmentsutils +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Functions for extracting internal Pygments data. + +This file was forked by the Kallithea project in July 2014. +Original author and date, and relevant copyright and licensing information is below: +:created_on: Jan 5, 2011 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH, and others. +:license: GPLv3, see LICENSE.md for more details. +""" + +from collections import defaultdict +from itertools import ifilter +from string import lower + +from pygments import lexers + + +def get_lem(): + """ + Get language extension map based on what's inside pygments lexers + """ + d = defaultdict(lambda: []) + + def __clean(s): + s = s.lstrip('*') + s = s.lstrip('.') + + if s.find('[') != -1: + exts = [] + start, stop = s.find('['), s.find(']') + + for suffix in s[start + 1:stop]: + exts.append(s[:s.find('[')] + suffix) + return map(lower, exts) + else: + return map(lower, [s]) + + for lx, t in sorted(lexers.LEXERS.items()): + m = map(__clean, t[-2]) + if m: + m = reduce(lambda x, y: x + y, m) + for ext in m: + desc = lx.replace('Lexer', '') + d[ext].append(desc) + + return dict(d) + + +def get_index_filenames(): + """ + Get list of known indexable filenames from pygment lexer internals + """ + + filenames = [] + + def likely_filename(s): + return s.find('*') == -1 and s.find('[') == -1 + + for lx, t in sorted(lexers.LEXERS.items()): + for f in ifilter(likely_filename, t[-2]): + filenames.append(f) + + return filenames + + +def get_custom_lexer(extension): + """ + returns a custom lexer if it's defined in rcextensions module, or None + if there's no custom lexer defined + """ + import kallithea + #check if we didn't define this extension as other lexer + if kallithea.EXTENSIONS and extension in kallithea.EXTENSIONS.EXTRA_LEXERS: + _lexer_name = kallithea.EXTENSIONS.EXTRA_LEXERS[extension] + return lexers.get_lexer_by_name(_lexer_name) diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/rcmail/smtp_mailer.py --- a/kallithea/lib/rcmail/smtp_mailer.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/rcmail/smtp_mailer.py Sat Dec 24 00:34:38 2016 +0100 @@ -60,9 +60,9 @@ self.debug = debug self.auth = smtp_auth - def send(self, recipients=[], subject='', body='', html='', + def send(self, recipients=None, subject='', body='', html='', attachment_files=None, headers=None): - + recipients = recipients or [] if isinstance(recipients, basestring): recipients = [recipients] if headers is None: diff -r b4dd4c16c12d -r d89d586b26ae kallithea/lib/utils.py --- a/kallithea/lib/utils.py Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/lib/utils.py Sat Dec 24 00:34:38 2016 +0100 @@ -37,15 +37,11 @@ import decorator import warnings from os.path import abspath -from os.path import dirname as dn, join as jn - -from paste.script.command import Command, BadCommand +from os.path import dirname from webhelpers.text import collapse, remove_formatting, strip_tags from beaker.cache import _cache_decorate -from kallithea import BRAND - from kallithea.lib.vcs.utils.hgcompat import ui, config from kallithea.lib.vcs.utils.helpers import get_scm from kallithea.lib.vcs.exceptions import VCSError @@ -53,7 +49,6 @@ from kallithea.model import meta from kallithea.model.db import Repository, User, Ui, \ UserLog, RepoGroup, Setting, UserGroup -from kallithea.model.meta import Session from kallithea.model.repo_group import RepoGroupModel from kallithea.lib.utils2 import safe_str, safe_unicode, get_current_authuser from kallithea.lib.vcs.utils.fakemod import create_module @@ -186,7 +181,7 @@ repo_obj = Repository.get_by_repo_name(repo_name) else: repo_obj = None - repo_name = '' + repo_name = u'' user_log = UserLog() user_log.user_id = user_obj.user_id @@ -206,7 +201,7 @@ sa.commit() -def get_filesystem_repos(path, recursive=False, skip_removed_repos=True): +def get_filesystem_repos(path): """ Scans given path for repos and return (name,(type,path)) tuple @@ -215,41 +210,51 @@ """ # remove ending slash for better results - path = path.rstrip(os.sep) - log.debug('now scanning in %s location recursive:%s...', path, recursive) + path = safe_str(path.rstrip(os.sep)) + log.debug('now scanning in %s', path) + + def isdir(*n): + return os.path.isdir(os.path.join(*n)) - def _get_repos(p): - if not os.access(p, os.R_OK) or not os.access(p, os.X_OK): - log.warning('ignoring repo path without access: %s', p) - return - if not os.access(p, os.W_OK): - log.warning('repo path without write access: %s', p) - for dirpath in os.listdir(p): - if os.path.isfile(os.path.join(p, dirpath)): + for root, dirs, _files in os.walk(path): + recurse_dirs = [] + for subdir in dirs: + # skip removed repos + if REMOVED_REPO_PAT.match(subdir): continue - cur_path = os.path.join(p, dirpath) - # skip removed repos - if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath): + #skip . dirs TODO: rly? then we should prevent creating them ... + if subdir.startswith('.'): continue - #skip . dirs - if dirpath.startswith('.'): - continue + cur_path = os.path.join(root, subdir) + if (isdir(cur_path, '.hg') or + isdir(cur_path, '.git') or + isdir(cur_path, '.svn') or + isdir(cur_path, 'objects') and (isdir(cur_path, 'refs') or + os.path.isfile(os.path.join(cur_path, 'packed-refs')))): + + if not os.access(cur_path, os.R_OK) or not os.access(cur_path, os.X_OK): + log.warning('ignoring repo path without access: %s', cur_path) + continue + + if not os.access(cur_path, os.W_OK): + log.warning('repo path without write access: %s', cur_path) - try: - scm_info = get_scm(cur_path) - yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info - except VCSError: - if not recursive: - continue - #check if this dir containts other repos for recursive scan - rec_path = os.path.join(p, dirpath) - if not os.path.islink(rec_path) and os.path.isdir(rec_path): - for inner_scm in _get_repos(rec_path): - yield inner_scm + try: + scm_info = get_scm(cur_path) + assert cur_path.startswith(path) + repo_path = cur_path[len(path) + 1:] + yield repo_path, scm_info + continue # no recursion + except VCSError: + # We should perhaps ignore such broken repos, but especially + # the bare git detection is unreliable so we dive into it + pass - return _get_repos(path) + recurse_dirs.append(subdir) + + dirs[:] = recurse_dirs def is_valid_repo(repo_name, base_path, scm=None): @@ -304,18 +309,6 @@ return False -def ask_ok(prompt, retries=4, complaint='Yes or no please!'): - while True: - ok = raw_input(prompt) - if ok in ('y', 'ye', 'yes'): - return True - if ok in ('n', 'no', 'nop', 'nope'): - return False - retries = retries - 1 - if retries < 0: - raise IOError - print complaint - #propagated from mercurial documentation ui_sections = ['alias', 'auth', 'decode/encode', 'defaults', @@ -365,21 +358,17 @@ hg_ui = ret for ui_ in hg_ui: if ui_.ui_active: - ui_val = safe_str(ui_.ui_value) - if ui_.ui_section == 'hooks' and BRAND != 'kallithea' and ui_val.startswith('python:' + BRAND + '.lib.hooks.'): - ui_val = ui_val.replace('python:' + BRAND + '.lib.hooks.', 'python:kallithea.lib.hooks.') - log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section, + ui_val = '' if ui_.ui_value is None else safe_str(ui_.ui_value) + log.debug('settings ui from db: [%s] %s=%r', ui_.ui_section, ui_.ui_key, ui_val) baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key), ui_val) - if ui_.ui_key == 'push_ssl': - # force set push_ssl requirement to False, kallithea - # handles that - baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key), - False) if clear_session: meta.Session.remove() + # force set push_ssl requirement to False, Kallithea handles that + baseui.setconfig('web', 'push_ssl', False) + baseui.setconfig('web', 'allow_push', '*') # prevent interactive questions for ssh password / passphrase ssh = baseui.config('ui', 'ssh', default='ssh') baseui.setconfig('ui', 'ssh', '%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh) @@ -389,7 +378,7 @@ def set_app_settings(config): """ - Updates pylons config with new settings from database + Updates app config with new settings from database :param config: """ @@ -418,6 +407,21 @@ 'utf8'), sep=',') +def set_indexer_config(config): + """ + Update Whoosh index mapping + + :param config: kallithea.CONFIG + """ + from kallithea.config import conf + + log.debug('adding extra into INDEX_EXTENSIONS') + conf.INDEX_EXTENSIONS.extend(re.split('\s+', config.get('index.extensions', ''))) + + log.debug('adding extra into INDEX_FILENAMES') + conf.INDEX_FILENAMES.extend(re.split('\s+', config.get('index.filenames', ''))) + + def map_groups(path): """ Given a full path to a repository, create all nested groups that this @@ -436,7 +440,7 @@ rgm = RepoGroupModel(sa) owner = User.get_first_admin() for lvl, group_name in enumerate(groups): - group_name = '/'.join(groups[:lvl] + [group_name]) + group_name = u'/'.join(groups[:lvl] + [group_name]) group = RepoGroup.get_by_group_name(group_name) desc = '%s group' % group_name @@ -449,7 +453,7 @@ lvl, group_name) group = RepoGroup(group_name, parent) group.group_description = desc - group.user = owner + group.owner = owner sa.add(group) perm_obj = rgm._create_default_perms(group) sa.add(perm_obj) @@ -528,8 +532,9 @@ removed = [] # remove from database those repositories that are not in the filesystem + unicode_initial_repo_list = set(safe_unicode(name) for name in initial_repo_list) for repo in sa.query(Repository).all(): - if repo.repo_name not in initial_repo_list.keys(): + if repo.repo_name not in unicode_initial_repo_list: if remove_obsolete: log.debug("Removing non-existing repository found in db `%s`", repo.repo_name) @@ -544,34 +549,6 @@ return added, removed -# set cache regions for beaker so celery can utilise it -def add_cache(settings): - cache_settings = {'regions': None} - for key in settings.keys(): - for prefix in ['beaker.cache.', 'cache.']: - if key.startswith(prefix): - name = key.split(prefix)[1].strip() - cache_settings[name] = settings[key].strip() - if cache_settings['regions']: - for region in cache_settings['regions'].split(','): - region = region.strip() - region_settings = {} - for key, value in cache_settings.items(): - if key.startswith(region): - region_settings[key.split('.')[1]] = value - region_settings['expire'] = int(region_settings.get('expire', - 60)) - region_settings.setdefault('lock_dir', - cache_settings.get('lock_dir')) - region_settings.setdefault('data_dir', - cache_settings.get('data_dir')) - - if 'type' not in region_settings: - region_settings['type'] = cache_settings.get('type', - 'memory') - beaker.cache.cache_regions[region] = region_settings - - def load_rcextensions(root_path): import kallithea from kallithea.config import conf @@ -603,19 +580,6 @@ # setattr(EXT, k, getattr(rcextensions, k)) -def get_custom_lexer(extension): - """ - returns a custom lexer if it's defined in rcextensions module, or None - if there's no custom lexer defined - """ - import kallithea - from pygments import lexers - #check if we didn't define this extension as other lexer - if kallithea.EXTENSIONS and extension in kallithea.EXTENSIONS.EXTRA_LEXERS: - _lexer_name = kallithea.EXTENSIONS.EXTRA_LEXERS[extension] - return lexers.get_lexer_by_name(_lexer_name) - - #============================================================================== # TEST FUNCTIONS AND CREATORS #============================================================================== @@ -637,9 +601,9 @@ os.makedirs(index_location) try: - l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock')) + l = DaemonLock(file_=os.path.join(dirname(index_location), 'make_index.lock')) WhooshIndexingDaemon(index_location=index_location, - repo_location=repo_location)\ + repo_location=repo_location) \ .run(full_index=full_index) l.release() except LockHeld: @@ -652,10 +616,10 @@ install test repository into tmp dir """ from kallithea.lib.db_manage import DbManage - from kallithea.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH + from kallithea.tests.base import HG_REPO, GIT_REPO, TESTS_TMP_PATH # PART ONE create db - dbconf = config['sqlalchemy.db1.url'] + dbconf = config['sqlalchemy.url'] log.debug('making test db %s', dbconf) # create test dir if it doesn't exist @@ -672,7 +636,7 @@ dbmanage.admin_prompt() dbmanage.create_permissions() dbmanage.populate_default_permissions() - Session().commit() + meta.Session().commit() # PART TWO make test repo log.debug('making test vcs repositories') @@ -689,14 +653,14 @@ shutil.rmtree(data_path) #CREATE DEFAULT TEST REPOS - cur_dir = dn(dn(abspath(__file__))) - tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_hg.tar.gz")) - tar.extractall(jn(TESTS_TMP_PATH, HG_REPO)) + cur_dir = dirname(dirname(abspath(__file__))) + tar = tarfile.open(os.path.join(cur_dir, 'tests', 'fixtures', "vcs_test_hg.tar.gz")) + tar.extractall(os.path.join(TESTS_TMP_PATH, HG_REPO)) tar.close() - cur_dir = dn(dn(abspath(__file__))) - tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_git.tar.gz")) - tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO)) + cur_dir = dirname(dirname(abspath(__file__))) + tar = tarfile.open(os.path.join(cur_dir, 'tests', 'fixtures', "vcs_test_git.tar.gz")) + tar.extractall(os.path.join(TESTS_TMP_PATH, GIT_REPO)) tar.close() #LOAD VCS test stuff @@ -704,87 +668,6 @@ setup_package() -#============================================================================== -# PASTER COMMANDS -#============================================================================== -class BasePasterCommand(Command): - """ - Abstract Base Class for paster commands. - - The celery commands are somewhat aggressive about loading - celery.conf, and since our module sets the `CELERY_LOADER` - environment variable to our loader, we have to bootstrap a bit and - make sure we've had a chance to load the pylons config off of the - command line, otherwise everything fails. - """ - min_args = 1 - min_args_error = "Please provide a paster config file as an argument." - takes_config_file = 1 - requires_config_file = True - - def notify_msg(self, msg, log=False): - """Make a notification to user, additionally if logger is passed - it logs this action using given logger - - :param msg: message that will be printed to user - :param log: logging instance, to use to additionally log this message - - """ - if log and isinstance(log, logging): - log(msg) - - def run(self, args): - """ - Overrides Command.run - - Checks for a config file argument and loads it. - """ - if len(args) < self.min_args: - raise BadCommand( - self.min_args_error % {'min_args': self.min_args, - 'actual_args': len(args)}) - - # Decrement because we're going to lob off the first argument. - # @@ This is hacky - self.min_args -= 1 - self.bootstrap_config(args[0]) - self.update_parser() - return super(BasePasterCommand, self).run(args[1:]) - - def update_parser(self): - """ - Abstract method. Allows for the class's parser to be updated - before the superclass's `run` method is called. Necessary to - allow options/arguments to be passed through to the underlying - celery command. - """ - raise NotImplementedError("Abstract Method.") - - def bootstrap_config(self, conf): - """ - Loads the pylons configuration. - """ - from pylons import config as pylonsconfig - - self.path_to_ini_file = os.path.realpath(conf) - conf = paste.deploy.appconfig('config:' + self.path_to_ini_file) - pylonsconfig.init_app(conf.global_conf, conf.local_conf) - - def _init_session(self): - """ - Inits SqlAlchemy Session - """ - logging.config.fileConfig(self.path_to_ini_file) - from pylons import config - from kallithea.model import init_model - from kallithea.lib.utils2 import engine_from_config - - #get to remove repos !! - add_cache(config) - engine = engine_from_config(config, 'sqlalchemy.db1.') - init_model(engine) - - def check_git_version(): """ Checks what version of git is installed in system, and issues a warning @@ -835,15 +718,52 @@ pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8' data = func(*args, **kwargs) if isinstance(data, (list, tuple)): + # A JSON list response is syntactically valid JavaScript and can be + # loaded and executed as JavaScript by a malicious third-party site + # using -
-${c.users_log.pager('$link_previous ~2~ $link_next')} +
    + ${c.users_log.pager()} +
%else: ${_('No actions yet')} diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/auth/auth_settings.html --- a/kallithea/templates/admin/auth/auth_settings.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/auth/auth_settings.html Sat Dec 24 00:34:38 2016 +0100 @@ -16,9 +16,9 @@ <%def name="main()"> -
+
-
+
${self.breadcrumbs()}
${h.form(url('auth_settings'))} @@ -26,79 +26,86 @@ ## enabled auth plugins

${_('Authentication Plugins')}

-
-
-
-
${h.text("auth_plugins", class_='large')} - ${_('Comma-separated list of plugins; Kallithea will try user authentication in plugin order')} -
${_('Available built-in plugins')}
-
    - %for plugin_path in c.available_plugins: -
  • -
    - - ${_('Enabled') if plugin_path in c.enabled_plugins else _('Disabled')}${plugin_path} -
    +
    +
    + +
    + ${h.text("auth_plugins", class_='form-control')} + ${_('Comma-separated list of plugins; Kallithea will try user authentication in plugin order')} +
    +
    +
    + +
    +
      + %for plugin_path in c.available_plugins: +
    • + + ${_('Enabled') if plugin_path in c.enabled_plugins else _('Disabled')} + + ${plugin_path}
    • - %endfor -
    -
    -
    + %endfor +
+
+
%for cnt, module in enumerate(c.enabled_plugins): <% pluginName = c.plugin_shortnames[module] %>

${_('Plugin')}: ${pluginName}

-
+
## autoform generation, based on plugin definition from it's settings %for setting in c.plugin_settings[module]: <% fullsetting = "auth_%s_%s" % (pluginName, setting["name"]) %> <% displayname = (setting["formname"] if ("formname" in setting) else setting["name"]) %> %if setting["type"] == "password": -
-
-
- ${h.password(fullsetting,class_='small')} +
+ +
+ ${h.password(fullsetting,class_='form-control')} ${setting["description"]}
%elif setting["type"] in ["string", "int"]: -
-
-
- ${h.text(fullsetting,class_='small')} +
+ +
+ ${h.text(fullsetting,class_='form-control')} ${setting["description"]}
%elif setting["type"] == "bool": -
-
-
-
${h.checkbox(fullsetting,True,class_='small')}
+
+ +
+ ${h.checkbox(fullsetting,True)} ${setting["description"]}
%elif setting["type"] == "select": -
-
-
- ${h.select(fullsetting,setting['values'][0],setting['values'],class_='small')} +
+ +
+ ${h.select(fullsetting,setting['values'][0],setting['values'],class_='form-control')} ${setting["description"]}
%else: -
-
-
This field is of type ${setting['type']}, which cannot be displayed. Must be one of [string|int|bool|select].
+
+ +
This field is of type ${setting['type']}, which cannot be displayed. Must be one of [string|int|bool|select].
${setting["description"]}
%endif %endfor
%endfor -
-
- ${h.submit('save',_('Save'),class_="btn")} +
+
+
+ ${h.submit('save',_('Save'),class_="btn btn-default")} +
@@ -115,19 +122,19 @@ var $cur_button = $(e.currentTarget); var plugin_id = $cur_button.attr('plugin_id'); - if($cur_button.hasClass('btn-success')){ + if($cur_button.hasClass('active')){ elems.splice(elems.indexOf(plugin_id), 1); $auth_plugins_input.val(elems.join(',')); - $cur_button.removeClass('btn-success'); - $cur_button.html(_TM['disabled']); + $cur_button.removeClass('active'); + $cur_button.html(_TM['Disabled']); } else{ if(elems.indexOf(plugin_id) == -1){ elems.push(plugin_id); } $auth_plugins_input.val(elems.join(',')); - $cur_button.addClass('btn-success'); - $cur_button.html(_TM['enabled']); + $cur_button.addClass('active'); + $cur_button.html(_TM['Enabled']); } }); diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/defaults/defaults.html --- a/kallithea/templates/admin/defaults/defaults.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/defaults/defaults.html Sat Dec 24 00:34:38 2016 +0100 @@ -16,70 +16,61 @@ <%def name="main()"> -
+
-
+
${self.breadcrumbs()}
- ${h.form(url('default', id='defaults'),method='put')} + ${h.form(url('defaults_update', id='defaults'))}
-
+
-
-
- -
-
- ${h.select('default_repo_type','hg',c.backends,class_="medium")} +
+ +
+ ${h.select('default_repo_type','hg',c.backends,class_='form-control')}
-
-
- -
-
+
+ +
${h.checkbox('default_repo_private',value="True")} ${_('Private repositories are only visible to people explicitly added as collaborators.')}
- -
-
- -
-
+
+ +
${h.checkbox('default_repo_enable_statistics',value="True")} ${_('Enable statistics window on summary page.')}
-
-
- -
-
+
+ +
${h.checkbox('default_repo_enable_downloads',value="True")} ${_('Enable download menu on summary page.')}
-
-
- -
-
+
+ +
${h.checkbox('default_repo_enable_locking',value="True")} ${_('Enable lock-by-pulling on repository.')}
-
- ${h.submit('save',_('Save'),class_="btn")} +
+
+ ${h.submit('save',_('Save'),class_="btn btn-default")} +
diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/gists/edit.html --- a/kallithea/templates/admin/gists/edit.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/gists/edit.html Sat Dec 24 00:34:38 2016 +0100 @@ -23,9 +23,9 @@ <%def name="main()"> -
+
-
+
${self.breadcrumbs()}
@@ -46,17 +46,17 @@
${h.form(h.url('edit_gist', gist_id=c.gist.gist_access_id), method='post', id='eform')}
-
- ${h.gravatar(c.authuser.email, size=32)} -
+ ${h.gravatar_div(c.authuser.email, size=32)}
- - ${h.select('lifetime', '0', c.lifetime_options)} - + + %if c.gist.gist_expires == -1: ${_('Expires')}: ${_('Never')} %else: @@ -81,69 +81,70 @@ ## dynamic edit box. %endfor
- ${h.submit('update',_('Update Gist'),class_="btn btn-mini btn-success")} - ${_('Cancel')} + ${h.submit('update',_('Update Gist'),class_="btn btn-success")} + ${_('Cancel')}
${h.end_form()}
diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/gists/show.html --- a/kallithea/templates/admin/gists/show.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/gists/show.html Sat Dec 24 00:34:38 2016 +0100 @@ -15,14 +15,14 @@ <%def name="main()"> -
+
-
+
${self.breadcrumbs()} %if c.authuser.username != 'default': %endif @@ -34,45 +34,43 @@
%if c.gist.gist_type == 'public': -
${_('Public Gist')}
+
${_('Public Gist')}
%else: -
${_('Private Gist')}
+
${_('Private Gist')}
%endif
${c.gist.gist_description}
-
+
%if c.gist.gist_expires == -1: ${_('Expires')}: ${_('Never')} %else: ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))} %endif -
+
- %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.authuser.user_id: + %if h.HasPermissionAny('hg.admin')() or c.gist.owner_id == c.authuser.user_id:
- ${h.form(url('gist', gist_id=c.gist.gist_id),method='delete')} - ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")} + ${h.form(url('gist_delete', gist_id=c.gist.gist_id))} + ${h.submit('remove_gist', _('Delete'),class_="btn btn-danger btn-xs",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")} ${h.end_form()}
%endif
## only owner should see that - %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.authuser.user_id: - ${h.link_to(_('Edit'),h.url('edit_gist', gist_id=c.gist.gist_access_id),class_="btn btn-mini")} + %if h.HasPermissionAny('hg.admin')() or c.gist.owner_id == c.authuser.user_id: + ${h.link_to(_('Edit'),h.url('edit_gist', gist_id=c.gist.gist_access_id),class_="btn btn-default btn-xs")} %endif - ${h.link_to(_('Show as Raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="btn btn-mini")} + ${h.link_to(_('Show as Raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="btn btn-default btn-xs")}
-
- ${h.gravatar(h.email_or_none(c.file_changeset.author), size=16)} -
+ ${h.gravatar_div(h.email_or_none(c.file_changeset.author), size=16)}
${h.person(c.file_changeset.author)} - ${_('created')} ${h.age(c.file_changeset.date)}
-
${h.urlify_commit(c.file_changeset.message,c.repo_name)}
+
${h.urlify_text(c.file_changeset.message,c.repo_name)}
@@ -83,7 +81,7 @@ ${h.safe_unicode(file.path)}
- ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=h.safe_unicode(file.path)),class_="btn btn-mini")} + ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=h.safe_unicode(file.path)),class_="btn btn-default btn-xs")}
diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/my_account/my_account.html --- a/kallithea/templates/admin/my_account/my_account.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/my_account/my_account.html Sat Dec 24 00:34:38 2016 +0100 @@ -14,31 +14,29 @@ <%def name="main()"> -
-
+
+
${self.breadcrumbs()}
##main
diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/my_account/my_account_api_keys.html --- a/kallithea/templates/admin/my_account/my_account_api_keys.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/my_account/my_account_api_keys.html Sat Dec 24 00:34:38 2016 +0100 @@ -3,14 +3,14 @@
${c.user.api_key}
- ${_('Built-in')} + ${_('Built-in')} ${_('Expires')}: ${_('Never')} - ${h.form(url('my_account_api_keys'),method='delete')} + ${h.form(url('my_account_api_keys_delete'))} ${h.hidden('del_api_key',c.user.api_key)} ${h.hidden('del_api_key_builtin',1)} - @@ -34,9 +34,9 @@ %endif - ${h.form(url('my_account_api_keys'),method='delete')} + ${h.form(url('my_account_api_keys_delete'))} ${h.hidden('del_api_key',api_key.api_key)} - %else: - %endif
-
+
  • ${_('All actions done in this repository will be visible to everyone in the public journal.')}
@@ -44,11 +44,11 @@ ${h.end_form()}

${_('Change Locking')}

-${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')} +${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name))}
%if c.repo_info.locked[0]: ${h.hidden('set_unlock', '1')} - ${_('Repository is not locked')} %endif -
+
  • ${_('Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository. The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push.')}
  • @@ -73,19 +73,25 @@ ${h.end_form()}

    ${_('Delete')}

    -${h.form(url('delete_repo', repo_name=c.repo_name), method='delete')} +${h.form(url('delete_repo', repo_name=c.repo_name))}
    - %if c.repo_info.forks.count(): ${ungettext('This repository has %s fork', 'This repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()} - - + + %endif -
    +
    • ${_('The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it.')}
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repo_edit_caches.html --- a/kallithea/templates/admin/repos/repo_edit_caches.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/repos/repo_edit_caches.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,16 +1,16 @@ -${h.form(url('edit_repo_caches', repo_name=c.repo_name), method='put')} +${h.form(url('update_repo_caches', repo_name=c.repo_name))}
    - ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate Repository Cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache.')+"');")} -
    + ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate Repository Cache'),class_="btn btn-default btn-sm")} +
    • ${_('Manually invalidate cache for this repository. On first access, the repository will be cached again.')}
    -
    +
    ${_('List of Cached Values')} - +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repo_edit_fields.html --- a/kallithea/templates/admin/repos/repo_edit_fields.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/repos/repo_edit_fields.html Sat Dec 24 00:34:38 2016 +0100 @@ -13,10 +13,10 @@ @@ -24,39 +24,36 @@
    ${_('Prefix')} ${_('Key')}${field.field_key} ${field.field_type} - ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')} + ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id))} ${h.submit('remove_%s' % field.repo_field_id, _('Delete'), id="remove_field_%s" % field.repo_field_id, - class_="action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")} + class_="btn btn-default btn-xs", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")} ${h.end_form()}
    %endif - ${h.form(url('create_repo_fields', repo_name=c.repo_name),method='put')} + ${h.form(url('create_repo_fields', repo_name=c.repo_name))}
    -
    -
    -
    - +
    +
    + +
    + ${h.text('new_field_key', class_='form-control')}
    -
    - ${h.text('new_field_key', class_='small')} -
    -
    -
    -
    - +
    + +
    + +
    + ${h.text('new_field_label', class_='form-control', placeholder=_('Enter short label'))}
    -
    - ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))} -
    -
    +
    -
    -
    - +
    + +
    + ${h.text('new_field_desc', class_='form-control', placeholder=_('Enter description of a field'))}
    -
    - ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))} +
    + +
    +
    + ${h.submit('save',_('Add'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")}
    -
    - -
    - ${h.submit('save',_('Add'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")}
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repo_edit_fork.html --- a/kallithea/templates/admin/repos/repo_edit_fork.html Wed Dec 21 14:56:09 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -${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_="btn btn-small")} -
    -
    -
      -
    • ${_('''Manually set this repository as a fork of another from the list.''')}
    • -
    -
    -
    -${h.end_form()} - - diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repo_edit_permissions.html --- a/kallithea/templates/admin/repos/repo_edit_permissions.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/repos/repo_edit_permissions.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,7 +1,7 @@ -${h.form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')} +${h.form(url('edit_repo_perms_update', repo_name=c.repo_name))}
    -
    -
    +
    +
    ${h.hidden('repo_private')} @@ -39,7 +39,7 @@ @@ -72,7 +72,7 @@ %endfor <% - _tmpl = h.literal("""' \ + _tmpl = h.literal("""'\ \ \ \ @@ -97,11 +97,11 @@
    %if r2p.user.username !='default': - + ${_('Revoke')} %endif @@ -64,7 +64,7 @@ %endif - + ${_('Revoke')}
    -
    - ${h.submit('save',_('Save'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")} +
    + ${h.submit('save',_('Save'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")}
    -
    +
    ${h.end_form()} diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repo_edit_remote.html --- a/kallithea/templates/admin/repos/repo_edit_remote.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/repos/repo_edit_remote.html Sat Dec 24 00:34:38 2016 +0100 @@ -2,12 +2,12 @@
    ${_('Remote repository URL')}: ${c.repo_info.clone_uri_hidden}
    -${h.form(url('edit_repo_remote', repo_name=c.repo_name), method='put')} +${h.form(url('edit_repo_remote_update', repo_name=c.repo_name))}
    ${h.submit('remote_pull_%s' % c.repo_info.repo_name, _('Pull Changes from Remote Repository'), - class_="btn btn-small", + class_="btn btn-default btn-sm", onclick="return confirm('"+_('Confirm to pull changes from remote repository.')+"');")}
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repo_edit_settings.html --- a/kallithea/templates/admin/repos/repo_edit_settings.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/repos/repo_edit_settings.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,13 +1,11 @@ -${h.form(url('put_repo', repo_name=c.repo_info.repo_name), method='put')} +${h.form(url('update_repo', repo_name=c.repo_info.repo_name))}
    -
    -
    -
    - -
    -
    - ${h.text('repo_name',class_="medium")} +
    +
    + +
    + ${h.text('repo_name',class_='form-control')} ${_('Permanent Repository ID')}: `_${c.repo_info.repo_id}` ${_('What is that?')}
    -
    -
    - -
    -
    +
    + +
    - ${h.text('clone_uri',class_="medium", placeholder=_('Repository URL'))} + ${h.text('clone_uri',class_='form-control', placeholder=_('Repository URL'))} ${h.hidden('clone_uri_hidden', c.repo_info.clone_uri_hidden)}
    @@ -30,78 +26,62 @@
    -
    -
    - -
    -
    - ${h.select('repo_group','',c.repo_groups,class_="medium")} +
    + +
    + ${h.select('repo_group','',c.repo_groups,class_='form-control')} ${_('Optionally select a group to put this repository into.')}
    -
    -
    - -
    -
    - ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")} +
    + +
    + ${h.select('repo_landing_rev','',c.landing_revs,class_='form-control')} ${_('Default revision for files page, downloads, whoosh and readme')}
    -
    -
    - -
    -
    +
    + +
    - ${h.text('user',class_='yui-ac-input')} + ${h.text('owner',class_='yui-ac-input form-control')} ${_('Change owner of this repository.')}
    -
    -
    - -
    -
    - ${h.textarea('repo_description', style="height:165px")} +
    + +
    + ${h.textarea('repo_description',class_='form-control',style="height:165px")} ${_('Keep it short and to the point. Use a README file for longer descriptions.')}
    -
    -
    - -
    -
    +
    + +
    ${h.checkbox('repo_private',value="True")} ${_('Private repositories are only visible to people explicitly added as collaborators.')}
    -
    -
    - -
    -
    +
    + +
    ${h.checkbox('repo_enable_statistics',value="True")} ${_('Enable statistics window on summary page.')}
    -
    -
    - -
    -
    +
    + +
    ${h.checkbox('repo_enable_downloads',value="True")} ${_('Enable download menu on summary page.')}
    -
    -
    - -
    -
    +
    + +
    ${h.checkbox('repo_enable_locking',value="True")} ${_('Enable lock-by-pulling on repository.')}
    @@ -110,12 +90,10 @@ %if c.visual.repository_fields: ## EXTRA FIELDS %for field in c.repo_fields: -
    -
    - -
    -
    - ${h.text(field.field_key_prefixed, field.field_value, class_='medium')} +
    + +
    + ${h.text(field.field_key_prefixed, field.field_value, class_='form-control')} %if field.field_desc: ${field.field_desc} %endif @@ -123,9 +101,11 @@
    %endfor %endif -
    - ${h.submit('save',_('Save'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")} +
    +
    + ${h.submit('save',_('Save'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")} +
    @@ -147,6 +127,6 @@ // autocomplete var _USERS_AC_DATA = ${c.users_array|n}; - SimpleUserAutoComplete($('#user'), $('#owner_container'), _USERS_AC_DATA); + SimpleUserAutoComplete($('#owner'), $('#owner_container'), _USERS_AC_DATA); }); diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repo_edit_statistics.html --- a/kallithea/templates/admin/repos/repo_edit_statistics.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/repos/repo_edit_statistics.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,13 +1,13 @@ -${h.form(url('edit_repo_statistics', repo_name=c.repo_info.repo_name), method='put')} +${h.form(url('edit_repo_statistics_update', repo_name=c.repo_info.repo_name))}
    -
    -
    +
    +
    • ${_('Processed commits')}: ${c.stats_revision}/${c.repo_last_rev}
    • ${_('Processed progress')}: ${c.stats_percentage}%
    - ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset Statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics.')+"');")} + ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset Statistics'),class_="btn btn-default btn-sm",onclick="return confirm('"+_('Confirm to remove current statistics.')+"');")}
    ${h.end_form()} diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/repos/repos.html --- a/kallithea/templates/admin/repos/repos.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/repos/repos.html Sat Dec 24 00:34:38 2016 +0100 @@ -6,54 +6,47 @@ <%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('Repositories')} + 0 ${_('Repositories')} <%block name="header_menu"> ${self.menu('admin')} <%def name="main()"> -
    +
    -
    +
    ${self.breadcrumbs()}
    -
    -
    - +
    +
    +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings.html --- a/kallithea/templates/admin/settings/settings.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings.html Sat Dec 24 00:34:38 2016 +0100 @@ -16,8 +16,8 @@ <%def name="main()"> -
    -
    +
    +
    ${self.breadcrumbs()}
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_email.html --- a/kallithea/templates/admin/settings/settings_email.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_email.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,18 +1,18 @@ ${h.form(url('admin_settings_email'), method='post')}
    -
    -
    -
    - -
    -
    - ${h.text('test_email',size=30)} +
    +
    + +
    + ${h.text('test_email',size=30,class_='form-control')}
    -
    - ${h.submit('send',_('Send'),class_="btn")} +
    +
    + ${h.submit('send',_('Send'),class_="btn btn-default")} +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_global.html --- a/kallithea/templates/admin/settings/settings_global.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_global.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,60 +1,57 @@ ${h.form(url('admin_settings_global'), method='post')}
    -
    +
    -
    -
    - -
    -
    - ${h.text('title',size=30)} +
    + +
    + ${h.text('title',size=30,class_='form-control')} ${_('Set a custom title for your Kallithea Service.')}
    -
    -
    - -
    -
    - ${h.text('realm',size=30)} +
    + +
    + ${h.text('realm',size=30,class_='form-control')}
    -
    -
    - -
    -
    - ${h.textarea('ga_code', cols=80, rows=10)} - ${_('HTML with JavaScript for web analytics systems like Google Analytics or Piwik. This will be added at the bottom of every page.')} +
    + +
    + ${h.textarea('ga_code', cols=80, rows=10,class_='form-control')} + ${_('HTML (possibly with \ + JavaScript and/or CSS) that will be added to the bottom \ + of every page. This can be used for web analytics \ + systems like Google Analytics or Piwik, but also to \ + perform instance-specific customizations like adding a \ + project banner at the top of every page.')}
    -
    -
    - -
    -
    - ${h.text('captcha_public_key',size=60)} +
    + +
    + ${h.text('captcha_public_key',size=60,class_='form-control')} ${_('Public key for reCaptcha system.')}
    -
    -
    - -
    -
    - ${h.text('captcha_private_key',size=60)} +
    + +
    + ${h.text('captcha_private_key',size=60,class_='form-control')} ${_('Private key for reCaptcha system. Setting this value will enable captcha on registration.')}
    -
    - ${h.submit('save',_('Save Settings'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")} +
    +
    + ${h.submit('save',_('Save Settings'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")} +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_hooks.html --- a/kallithea/templates/admin/settings/settings_hooks.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_hooks.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,13 +1,11 @@

    ${_('Built-in Mercurial Hooks (Read-Only)')}

    -
    +
    % for hook in c.hooks: -
    -
    - -
    -
    - ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly")} +
    + +
    + ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly",class_='form-control')}
    % endfor @@ -19,39 +17,37 @@

    ${_('Custom Hooks')}

    ${h.form(url('admin_settings_hooks'), method='post')}
    -
    +
    - % for hook in c.custom_hooks: -
    -
    - -
    -
    - ${h.hidden('hook_ui_key',hook.ui_key)} - ${h.hidden('hook_ui_value',hook.ui_value)} - ${h.text('hook_ui_value_new',hook.ui_value,size=60)} - - - ${_('Delete')} - + %for hook in c.custom_hooks: +
    + +
    + ${h.hidden('hook_ui_key',hook.ui_key)} + ${h.hidden('hook_ui_value',hook.ui_value)} + ${h.text('hook_ui_value_new',hook.ui_value,size=60,class_='form-control')} + + + ${_('Delete')} + +
    +
    + %endfor + +
    + +
    + ${h.text('new_hook_ui_value',size=60,class_='form-control')} +
    -
    - % endfor - -
    -
    -
    - ${h.text('new_hook_ui_key',size=20)} -
    +
    +
    + ${h.submit('save',_('Save'),class_="btn btn-default")} +
    -
    - ${h.text('new_hook_ui_value',size=60)} -
    -
    -
    - ${h.submit('save',_('Save'),class_="btn")} -
    ${h.end_form()} @@ -59,14 +55,14 @@ diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_mapping.html --- a/kallithea/templates/admin/settings/settings_mapping.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_mapping.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,38 +1,46 @@ ${h.form(url('admin_settings_mapping'), method='post')}
    -
    -
    -
    - -
    -
    +
    +
    + +
    - ${h.checkbox('destroy',True)} - +
    ${_('Check this option to remove all comments, pull requests and other records related to repositories that no longer exist in the filesystem.')}
    - ${h.checkbox('invalidate',True)} - +
    ${_('Check this to reload data and clear cache keys for all repositories.')}
    - ${h.checkbox('hooks',True)} - +
    ${_("Verify if Kallithea's Git hooks are installed for each repository. Current hooks will be updated to the latest version.")}
    - ${h.checkbox('hooks_overwrite', True)} - +
    ${_("If installing Git hooks, overwrite any existing hooks, even if they do not seem to come from Kallithea. WARNING: This operation will destroy any custom git hooks you may have deployed by hand!")}
    -
    - ${h.submit('rescan',_('Rescan Repositories'),class_="btn")} +
    +
    + ${h.submit('rescan',_('Rescan Repositories'),class_="btn btn-default")} +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_search.html --- a/kallithea/templates/admin/settings/settings_search.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_search.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,24 +1,24 @@ ${h.form(url('admin_settings_search'), method='post')}
    -
    -
    -
    - -
    -
    +
    +
    + +
    - ${h.checkbox('full_index',True)} - - +
    ${_('This option completely reindexeses all of the repositories for proper fulltext search capabilities.')} -
    -
    - ${h.submit('reindex',_('Reindex'),class_="btn")} +
    +
    + ${h.submit('reindex',_('Reindex'),class_="btn btn-default")} +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_system.html --- a/kallithea/templates/admin/settings/settings_system.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_system.html Sat Dec 24 00:34:38 2016 +0100 @@ -7,7 +7,7 @@ (_('Platform'), c.platform, ''), (_('Git version'), c.git_version, ''), (_('Git path'), c.ini.get('git_path'), ''), - (_('Upgrade info endpoint'), h.literal('%s
    %s.' % (c.update_url, _('Note: please make sure this server can access this URL'))), ''), + (_('Upgrade info endpoint'), h.literal('%s
    %s.' % (c.update_url, _('Note: please make sure this server can access this URL'))), ''), ] %> diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_system_update.html --- a/kallithea/templates/admin/settings/settings_system_update.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_system_update.html Sat Dec 24 00:34:38 2016 +0100 @@ -17,11 +17,13 @@

    % if c.should_upgrade and c.important_notices: -
    Important notes for this release:
    +
    + Important notes for this release:
      % for notice in c.important_notices:
    • - ${notice}
    • % endfor
    +
    % endif
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_vcs.html --- a/kallithea/templates/admin/settings/settings_vcs.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_vcs.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,73 +1,68 @@ ${h.form(url('admin_settings'), method='post')}
    -
    -
    -
    - -
    -
    +
    +
    + +
    - ${h.checkbox('web_push_ssl', 'True')} - +
    - ${_('Activate to require SSL both pushing and pulling. If SSL certificate is missing, it will return an HTTP Error 406: Not Acceptable.')} -
    -
    - -
    -
    - -
    -
    - ${h.checkbox('hooks_changegroup_repo_size','True')} - +
    - ${h.checkbox('hooks_changegroup_push_logger','True')} - +
    - ${h.checkbox('hooks_outgoing_pull_logger','True')} - -
    -
    - ${h.checkbox('hooks_changegroup_update','True')} - +
    -
    -
    -
    - -
    -
    +
    +
    + +
    - ${h.checkbox('extensions_largefiles','True')} - +
    - ${h.checkbox('extensions_hgsubversion','True')} - +
    ${_('Requires hgsubversion library to be installed. Enables cloning of remote Subversion repositories while converting them to Mercurial.')} ##
    - ## ${h.checkbox('extensions_hggit','True')} - ## + ## ##
    ##${_('Requires hg-git library to be installed. Enables cloning of remote Git repositories while converting them to Mercurial.')}
    %if c.visual.allow_repo_location_change: -
    -
    - -
    -
    - ${h.text('paths_root_path',size=60,readonly="readonly")} - + +
    + ${h.text('paths_root_path',size=60,readonly="readonly",class_='form-control')} + -
    +
    ${_('Filesystem location where repositories are stored. After changing this value, a restart and rescan of the repository folder are both required.')}
    @@ -76,9 +71,11 @@ ## form still requires this but we cannot internally change it anyway ${h.hidden('paths_root_path',size=30,readonly="readonly")} %endif -
    - ${h.submit('save',_('Save Settings'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")} +
    +
    + ${h.submit('save',_('Save Settings'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")} +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/settings/settings_visual.html --- a/kallithea/templates/admin/settings/settings_visual.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/settings/settings_visual.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,102 +1,102 @@ ${h.form(url('admin_settings_visual'), method='post')}
    -
    +
    -
    -
    - -
    -
    +
    + +
    - ${h.checkbox('repository_fields','True')} - +
    ${_('Allows storing additional customized fields per repository.')} +
    - ${h.checkbox('show_version','True')} - +
    ${_('Shows or hides a version number of Kallithea displayed in the footer.')}
    - ${h.checkbox('use_gravatar','True')} - +
    -
    -
    -
    - ${h.text('gravatar_url', size=80)} - ${_('''Gravatar URL allows you to use another avatar server application. + ${h.text('gravatar_url', size=80, class_='form-control')} + ${_('''Gravatar URL allows you to use another avatar server application. The following variables of the URL will be replaced accordingly. {scheme} 'http' or 'https' sent from running Kallithea server, {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, {netloc} network location/server host of running Kallithea server''')} -
    -
    -
    -
    - ${h.text('clone_uri_tmpl', size=80)} - ${_('''Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'. - The following variables are available: - {scheme} 'http' or 'https' sent from running Kallithea server, - {user} current user username, - {netloc} network location/server host of running Kallithea server, - {repo} full repository name, - {repoid} ID of repository, can be used to contruct clone-by-id''')} -
    -
    -
    - -
    -
    - -
    -
    - ${h.text('dashboard_items',size=5)} - ${_('Number of items displayed in the main page dashboard before pagination is shown.')}
    -
    -
    - +
    + +
    + ${h.text('clone_uri_tmpl', size=80, class_='form-control')} + ${_('''Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'. + The following variables are available: + {scheme} 'http' or 'https' sent from running Kallithea server, + {user} current user username, + {netloc} network location/server host of running Kallithea server, + {repo} full repository name, + {repoid} ID of repository, can be used to construct clone-by-id''')}
    -
    - ${h.text('admin_grid_items',size=5)} +
    + +
    + +
    + ${h.text('dashboard_items',size=5,class_='form-control')} + ${_('Number of items displayed in the repository pages before pagination is shown.')} +
    +
    + +
    + +
    + ${h.text('admin_grid_items',size=5,class_='form-control')} ${_('Number of items displayed in the admin pages grids before pagination is shown.')}
    -
    -
    - -
    -
    +
    + +
    - ${h.checkbox('show_public_icon','True')} - +
    - ${h.checkbox('show_private_icon','True')} - +
    ${_('Show public/private icons next to repository names.')}
    -
    +
    -
    -
    - -
    -
    +
    + +
    ${h.checkbox('stylify_metatags','True')} -
    -
    + ${_('Parses meta tags from the repository description field and turns them into colored tags.')} +
    + ${_('Stylify recognised meta tags:')}
    • [featured] featured
    • [stale] stale
    • @@ -108,14 +108,15 @@
    • [see => URI] see => URI
    - ${_('Parses meta tags from the repository description field and turns them into colored tags.')}
    -
    +
    -
    - ${h.submit('save',_('Save Settings'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")} -
    +
    +
    + ${h.submit('save',_('Save Settings'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")} +
    +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/user_groups/user_group_add.html --- a/kallithea/templates/admin/user_groups/user_group_add.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/user_groups/user_group_add.html Sat Dec 24 00:34:38 2016 +0100 @@ -17,44 +17,40 @@ <%def name="main()"> -
    +
    -
    +
    ${self.breadcrumbs()}
    ${h.form(url('users_groups'))}
    -
    -
    -
    - -
    -
    - ${h.text('users_group_name',class_='small')} +
    +
    + +
    + ${h.text('users_group_name',class_='form-control')}
    -
    -
    -
    - -
    -
    - ${h.textarea('user_group_description')} +
    +
    + +
    + ${h.textarea('user_group_description',class_='form-control')} ${_('Short, optional description for this user group.')}
    -
    -
    -
    - -
    -
    +
    +
    + +
    ${h.checkbox('users_group_active',value=True, checked='checked')}
    -
    +
    -
    - ${h.submit('save',_('Save'),class_="btn")} +
    +
    + ${h.submit('save',_('Save'),class_="btn btn-default")} +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/user_groups/user_group_edit.html --- a/kallithea/templates/admin/user_groups/user_group_edit.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/user_groups/user_group_edit.html Sat Dec 24 00:34:38 2016 +0100 @@ -18,8 +18,8 @@ <%def name="main()"> -
    -
    +
    +
    ${self.breadcrumbs()}
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/user_groups/user_group_edit_advanced.html --- a/kallithea/templates/admin/user_groups/user_group_edit_advanced.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/user_groups/user_group_edit_advanced.html Sat Dec 24 00:34:38 2016 +0100 @@ -5,7 +5,7 @@ elems = [ (_('Members'), len(c.group_members_obj), ''), (_('Created on'), h.fmt_date(c.user_group.created_on), ''), - (_('Owner'), h.person(c.user_group.user), ''), + (_('Owner'), h.person(c.user_group.owner), ''), ] %> %for dt, dd, tt in elems: @@ -14,8 +14,8 @@ %endfor -${h.form(h.url('users_group', id=c.user_group.users_group_id),method='delete')} - @@ -34,9 +34,9 @@ %endif - ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')} + ${h.form(url('edit_user_api_keys_delete', id=c.user.user_id))} ${h.hidden('del_api_key',api_key.api_key)} -
    - ${h.form(url('edit_user_api_keys', id=c.user.user_id), method='post')} + ${h.form(url('edit_user_api_keys_update', id=c.user.user_id))}
    -
    -
    -
    - +
    +
    + +
    +
    + +
    + ${h.text('description', class_='form-control', placeholder=_('Description'))}
    -
    - ${h.text('description', class_='medium', placeholder=_('Description'))} +
    +
    + +
    ${h.select('lifetime', '', c.lifetime_options)}
    -
    -
    - ${h.submit('save',_('Add'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")} +
    +
    +
    + ${h.submit('save',_('Add'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")} +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/users/user_edit_emails.html --- a/kallithea/templates/admin/users/user_edit_emails.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/users/user_edit_emails.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,49 +1,53 @@
    - + %if c.visual.use_gravatar: + + %endif %if c.user_email_map: %for em in c.user_email_map: - + %if c.visual.use_gravatar: + + %endif %endfor %else: - + %endif
    ${h.gravatar(c.user.email, size=16)}
    ${h.gravatar_div(c.user.email, size=16)} - ${_('Primary')} + ${_('Primary')}
    ${h.gravatar(c.user.email, size=16)}
    ${h.gravatar_div(c.user.email, size=16)} - ${h.form(url('edit_user_emails', id=c.user.user_id),method='delete')} + ${h.form(url('edit_user_emails_delete', id=c.user.user_id))} ${h.hidden('del_email_id',em.email_id)} ${h.submit('remove_',_('Delete'),id="remove_email_%s" % em.email_id, - class_="action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")} + class_="btn btn-default btn-xs", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")} ${h.end_form()}
    ${_('No additional emails specified.')}
    ${_('No additional emails specified.')}
    - ${h.form(url('edit_user_emails', id=c.user.user_id),method='put')} + ${h.form(url('edit_user_emails_update', id=c.user.user_id))}
    -
    -
    -
    - +
    +
    + +
    + ${h.text('new_email', class_='form-control')}
    -
    - ${h.text('new_email', class_='medium')} +
    +
    +
    + ${h.submit('save',_('Add'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")}
    -
    -
    - ${h.submit('save',_('Add'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")}
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/users/user_edit_ips.html --- a/kallithea/templates/admin/users/user_edit_ips.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/users/user_edit_ips.html Sat Dec 24 00:34:38 2016 +0100 @@ -16,11 +16,11 @@
    ${ip.ip_addr}
    ${h.ip_range(ip.ip_addr)}
    - ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')} + ${h.form(url('edit_user_ips_delete', id=c.user.user_id))} ${h.hidden('del_ip_id',ip.ip_id)} ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id, - class_="action_button", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")} + class_="btn btn-default btn-xs", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")} ${h.end_form()} @@ -33,21 +33,21 @@
    - ${h.form(url('edit_user_ips', id=c.user.user_id),method='put')} + ${h.form(url('edit_user_ips_update', id=c.user.user_id))}
    -
    -
    -
    - +
    +
    + +
    + ${h.text('new_ip', class_='form-control')}
    -
    - ${h.text('new_ip', class_='medium')} +
    +
    +
    + ${h.submit('save',_('Add'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")}
    -
    -
    - ${h.submit('save',_('Add'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")}
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/users/user_edit_perms.html --- a/kallithea/templates/admin/users/user_edit_perms.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/users/user_edit_perms.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,5 +1,5 @@ <%namespace name="dpb" file="/base/default_perms_box.html"/> -${dpb.default_perms_box(url('edit_user_perms', id=c.user.user_id))} +${dpb.default_perms_box(url('edit_user_perms_update', id=c.user.user_id))} ## permissions overview <%namespace name="p" file="/base/perms_summary.html"/> diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/users/user_edit_profile.html --- a/kallithea/templates/admin/users/user_edit_profile.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/users/user_edit_profile.html Sat Dec 24 00:34:38 2016 +0100 @@ -1,8 +1,8 @@ -${h.form(url('update_user', id=c.user.user_id),method='put')} -
    -
    -
    -
    ${h.gravatar(c.user.email)}
    +${h.form(url('update_user', id=c.user.user_id))} +
    +
    +
    + ${h.gravatar_div(c.user.email)}

    %if c.visual.use_gravatar: ${_('Change avatar at')} gravatar.com @@ -15,104 +15,86 @@ [${_('Current IP')}: ${c.ip_addr}] %endif %endif -

    +
    -
    +
    -
    -
    - +
    + +
    + ${h.text('username',class_='form-control', readonly=c.readonly('username'))}
    -
    - ${h.text('username',class_='medium', readonly=c.readonly('username'))} -
    -
    +
    -
    -
    - -
    -
    - ${h.text('email',class_='medium', readonly=c.readonly('email'))} +
    + +
    + ${h.text('email',class_='form-control', readonly=c.readonly('email'))}
    -
    +
    -
    -
    - -
    -
    - ${h.text('extern_type',class_='medium',readonly="readonly")} +
    + +
    + ${h.text('extern_type',class_='form-control',readonly="readonly")}
    -
    +
    -
    -
    - +
    + +
    + ${h.text('extern_name',class_='form-control',readonly="readonly")}
    -
    - ${h.text('extern_name',class_='medium',readonly="readonly")} -
    -
    +
    -
    -
    - +
    + +
    + ${h.password('new_password',class_='form-control',readonly=c.readonly('password'))}
    -
    - ${h.password('new_password',class_='medium',readonly=c.readonly('password'))} -
    -
    +
    -
    -
    - +
    + +
    + ${h.password('password_confirmation',class_='form-control',readonly=c.readonly('password'))}
    -
    - ${h.password('password_confirmation',class_="medium",readonly=c.readonly('password'))} -
    -
    +
    -
    -
    - +
    + +
    + ${h.text('firstname',class_='form-control', readonly=c.readonly('firstname'))}
    -
    - ${h.text('firstname',class_='medium', readonly=c.readonly('firstname'))} -
    -
    +
    -
    -
    - -
    -
    - ${h.text('lastname',class_='medium', readonly=c.readonly('lastname'))} +
    + +
    + ${h.text('lastname',class_='form-control', readonly=c.readonly('lastname'))}
    -
    +
    -
    -
    - -
    -
    +
    + +
    ${h.checkbox('active',value=True, readonly=c.readonly('active'))}
    -
    +
    -
    -
    - -
    -
    +
    + +
    ${h.checkbox('admin',value=True, readonly=c.readonly('admin'))}
    -
    +
    -
    - ${h.submit('save',_('Save'),class_="btn")} - ${h.reset('reset',_('Reset'),class_="btn")} +
    +
    + ${h.submit('save',_('Save'),class_="btn btn-default")} + ${h.reset('reset',_('Reset'),class_="btn btn-default")} +
    -
    +
    ${h.end_form()} diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/admin/users/users.html --- a/kallithea/templates/admin/users/users.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/admin/users/users.html Sat Dec 24 00:34:38 2016 +0100 @@ -6,7 +6,6 @@ <%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('Users')} @@ -15,50 +14,43 @@ <%def name="main()"> -
    +
    -
    +
    ${self.breadcrumbs()}
    -
    -
    +
    +
    +
    diff -r b4dd4c16c12d -r d89d586b26ae kallithea/templates/base/base.html --- a/kallithea/templates/base/base.html Wed Dec 21 14:56:09 2016 +0000 +++ b/kallithea/templates/base/base.html Sat Dec 24 00:34:38 2016 +0100 @@ -55,7 +55,7 @@ <%def name="admin_menu()"> -
      +