Changeset - e5ccabbc3cd7
[Not reviewed]
Merge stable
6 315 6
Thomas De Schampheleire - 6 years ago 2020-05-02 21:20:43
thomas.de_schampheleire@nokia.com
release: merge default to stable for 0.6.0
70 files changed:
Changeset was too big and was cut off... Show full diff anyway
0 comments (0 inline, 0 general)
.eslintrc.js
Show inline comments
 
new file 100644
 
module.exports = {
 
    "env": {
 
        "browser": true,
 
        "es6": true,
 
        "jquery": true
 
    },
 
    "extends": "eslint:recommended",
 
    "globals": {
 
        "Atomics": "readonly",
 
        "SharedArrayBuffer": "readonly"
 
    },
 
    "parserOptions": {
 
        "ecmaVersion": 2018,
 
        "sourceType": "module"
 
    },
 
    "plugins": [
 
        "html"
 
    ],
 
    "rules": {
 
    }
 
};
Jenkinsfile
Show inline comments
 
@@ -9,10 +9,10 @@ node {
 
                              daysToKeepStr: '',
 
                              numToKeepStr: '']]]);
 
    if (isUnix()) {
 
        createvirtualenv = 'rm -r $JENKINS_HOME/venv/$JOB_NAME || true && virtualenv $JENKINS_HOME/venv/$JOB_NAME'
 
        createvirtualenv = 'rm -r $JENKINS_HOME/venv/$JOB_NAME || true && python3 -m venv $JENKINS_HOME/venv/$JOB_NAME'
 
        activatevirtualenv = '. $JENKINS_HOME/venv/$JOB_NAME/bin/activate'
 
    } else {
 
        createvirtualenv = 'rmdir /s /q %JENKINS_HOME%\\venv\\%JOB_NAME% || true && virtualenv %JENKINS_HOME%\\venv\\%JOB_NAME%'
 
        createvirtualenv = 'rmdir /s /q %JENKINS_HOME%\\venv\\%JOB_NAME% || true && python3 -m venv %JENKINS_HOME%\\venv\\%JOB_NAME%'
 
        activatevirtualenv = 'call %JENKINS_HOME%\\venv\\%JOB_NAME%\\Scripts\\activate.bat'
 
    }
 

	
README.rst
Show inline comments
 
@@ -24,8 +24,8 @@ Kallithea was forked from RhodeCode in J
 
Installation
 
------------
 

	
 
Kallithea requires Python_ 2.7 and it is recommended to install it in a
 
virtualenv_. Official releases of Kallithea can be installed with::
 
Kallithea requires Python_ 3 and it is recommended to install it in a
 
virtualenv. Official releases of Kallithea can be installed with::
 

	
 
    pip install kallithea
 

	
 
@@ -173,7 +173,6 @@ database from RhodeCode to Kallithea, be
 
of Kallithea.
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _Python: http://www.python.org/
 
.. _Sphinx: http://sphinx.pocoo.org/
 
.. _Mercurial: http://mercurial.selenic.com/
conftest.py
Show inline comments
 
@@ -2,10 +2,19 @@ import os
 

	
 
import mock
 
import pytest
 
import tg
 

	
 

	
 
here = os.path.dirname(__file__)
 

	
 
# HACK:
 
def pytest_configure():
 
    # Register global dummy tg.context to avoid "TypeError: No object (name: context) has been registered for this thread"
 
    tg.request_local.context._push_object(tg.util.bunch.Bunch())
 
    # could be removed again after use with
 
    # tg.request_local.context._pop_object ... but we keep it around forever as
 
    # a reasonable sentinel
 

	
 
def pytest_ignore_collect(path):
 
    # ignore all files outside the 'kallithea' directory
 
    if not str(path).startswith(os.path.join(here, 'kallithea')):
 
@@ -36,3 +45,10 @@ def doctest_mock_ugettext(request):
 
    m = __import__(request.module.__name__, globals(), locals(), [None], 0)
 
    with mock.patch.object(m, '_', lambda s: s):
 
        yield
 

	
 
if getattr(pytest, 'register_assert_rewrite', None):
 
    # make sure that all asserts under kallithea/tests benefit from advanced
 
    # assert reporting with pytest-3.0.0+, including api/api_base.py,
 
    # models/common.py etc.
 
    # See also: https://docs.pytest.org/en/latest/assert.html#advanced-assertion-introspection
 
    pytest.register_assert_rewrite('kallithea.tests')
dev_requirements.txt
Show inline comments
 
pytest >= 4.6.6, < 4.7
 
pytest >= 4.6.6, < 5.4
 
pytest-sugar >= 0.9.2, < 0.10
 
pytest-benchmark >= 3.2.2, < 3.3
 
pytest-localserver >= 0.5.0, < 0.6
 
mock >= 3.0.0, < 3.1
 
Sphinx >= 1.8.0, < 1.9
 
WebTest >= 2.0.3, < 2.1
 
mock >= 3.0.0, < 4.1
 
Sphinx >= 1.8.0, < 2.4
 
WebTest >= 2.0.6, < 2.1
 
isort == 4.3.21
 
pyflakes == 2.1.1
development.ini
Show inline comments
 
################################################################################
 
################################################################################
 
# Kallithea - config file generated with kallithea-config                      #
 
#                                                                              #
 
# The %(here)s variable will be replaced with the parent directory of this file#
 
################################################################################
 
################################################################################
 
###################################################################################
 
###################################################################################
 
## Kallithea config file generated with kallithea-config                         ##
 
##                                                                               ##
 
## The %(here)s variable will be replaced with the parent directory of this file ##
 
###################################################################################
 
###################################################################################
 

	
 
[DEFAULT]
 

	
 
@@ -126,7 +126,7 @@ commit_parse_limit = 25
 
## used, which is correct in many cases but for example not when using uwsgi.
 
## If you change this setting, you should reinstall the Git hooks via
 
## Admin > Settings > Remap and Rescan.
 
# git_hook_interpreter = /srv/kallithea/venv/bin/python2
 
#git_hook_interpreter = /srv/kallithea/venv/bin/python3
 

	
 
## path to git executable
 
git_path = git
 
@@ -198,7 +198,7 @@ issue_sub =
 
## issue_pat, issue_server_link and issue_sub can have suffixes to specify
 
## multiple patterns, to other issues server, wiki or others
 
## below an example how to create a wiki pattern
 
# wiki-some-id -> https://wiki.example.com/some-id
 
## wiki-some-id -> https://wiki.example.com/some-id
 

	
 
#issue_pat_wiki = wiki-(\S+)
 
#issue_server_link_wiki = https://wiki.example.com/\1
 
@@ -250,25 +250,23 @@ ssh_enabled = false
 
###        CELERY CONFIG        ####
 
####################################
 

	
 
## Note: Celery doesn't support Windows.
 
use_celery = false
 

	
 
## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq:
 
broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost
 
## Celery config settings from https://docs.celeryproject.org/en/4.4.0/userguide/configuration.html prefixed with 'celery.'.
 

	
 
celery.imports = kallithea.lib.celerylib.tasks
 
celery.accept.content = pickle
 
celery.result.backend = amqp
 
celery.result.dburi = amqp://
 
celery.result.serializer = json
 
## Example: use the message queue on the local virtual host 'kallitheavhost' as the RabbitMQ user 'kallithea':
 
celery.broker_url = amqp://kallithea:thepassword@localhost:5672/kallitheavhost
 

	
 
#celery.send.task.error.emails = true
 
celery.result.backend = db+sqlite:///celery-results.db
 

	
 
#celery.amqp.task.result.expires = 18000
 

	
 
celeryd.concurrency = 2
 
celeryd.max.tasks.per.child = 1
 
celery.worker_concurrency = 2
 
celery.worker_max_tasks_per_child = 1
 

	
 
## If true, tasks will never be sent to the queue, but executed locally instead.
 
celery.always.eager = false
 
celery.task_always_eager = false
 

	
 
####################################
 
###         BEAKER CACHE        ####
 
@@ -277,19 +275,15 @@ celery.always.eager = false
 
beaker.cache.data_dir = %(here)s/data/cache/data
 
beaker.cache.lock_dir = %(here)s/data/cache/lock
 

	
 
beaker.cache.regions = short_term,long_term,sql_cache_short
 

	
 
beaker.cache.short_term.type = memory
 
beaker.cache.short_term.expire = 60
 
beaker.cache.short_term.key_length = 256
 
beaker.cache.regions = long_term,long_term_file
 

	
 
beaker.cache.long_term.type = memory
 
beaker.cache.long_term.expire = 36000
 
beaker.cache.long_term.key_length = 256
 

	
 
beaker.cache.sql_cache_short.type = memory
 
beaker.cache.sql_cache_short.expire = 10
 
beaker.cache.sql_cache_short.key_length = 256
 
beaker.cache.long_term_file.type = file
 
beaker.cache.long_term_file.expire = 604800
 
beaker.cache.long_term_file.key_length = 256
 

	
 
####################################
 
###       BEAKER SESSION        ####
 
@@ -324,12 +318,25 @@ session.secret = development-not-secret
 
#session.sa.url = postgresql://postgres:qwe@localhost/kallithea
 
#session.table_name = db_session
 

	
 
############################
 
## ERROR HANDLING SYSTEMS ##
 
############################
 
####################################
 
###       ERROR HANDLING        ####
 
####################################
 

	
 
## Show a nice error page for application HTTP errors and exceptions (default true)
 
#errorpage.enabled = true
 

	
 
# Propagate email settings to ErrorReporter of TurboGears2
 
# You do not normally need to change these lines
 
## Enable Backlash client-side interactive debugger (default false)
 
## WARNING: *THIS MUST BE false IN PRODUCTION ENVIRONMENTS!!!*
 
## This debug mode will allow all visitors to execute malicious code.
 
#debug = false
 
debug = true
 

	
 
## Enable Backlash server-side error reporting (unless debug mode handles it client-side) (default true)
 
#trace_errors.enable = true
 
## Errors will be reported by mail if trace_errors.error_email is set.
 

	
 
## Propagate email settings to ErrorReporter of TurboGears2
 
## You do not normally need to change these lines
 
get trace_errors.smtp_server = smtp_server
 
get trace_errors.smtp_port = smtp_port
 
get trace_errors.from_address = error_email_from
 
@@ -338,13 +345,6 @@ get trace_errors.smtp_username = smtp_us
 
get trace_errors.smtp_password = smtp_password
 
get trace_errors.smtp_use_tls = smtp_use_tls
 

	
 
################################################################################
 
## WARNING: *DEBUG MODE MUST BE OFF IN A PRODUCTION ENVIRONMENT*              ##
 
## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
 
## execute malicious code after an exception is raised.                       ##
 
################################################################################
 
#debug = false
 
debug = true
 

	
 
##################################
 
###       LOGVIEW CONFIG       ###
 
@@ -358,10 +358,10 @@ logview.pylons.util = #eee
 
### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###
 
#########################################################
 

	
 
# SQLITE [default]
 
## SQLITE [default]
 
sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
 

	
 
# see sqlalchemy docs for others
 
## see sqlalchemy docs for other backends
 

	
 
sqlalchemy.pool_recycle = 3600
 

	
 
@@ -392,9 +392,8 @@ keys = generic, color_formatter, color_f
 
[logger_root]
 
level = NOTSET
 
#handlers = console
 
## For coloring based on log level:
 
handlers = console_color
 
# For coloring based on log level:
 
# handlers = console_color
 

	
 
[logger_routes]
 
#level = WARN
 
@@ -437,7 +436,7 @@ qualname = gearbox
 
level = WARN
 
handlers =
 
qualname = sqlalchemy.engine
 
# For coloring based on log level and pretty printing of SQL:
 
## For coloring based on log level and pretty printing of SQL:
 
# level = INFO
 
# handlers = console_color_sql
 
# propagate = 0
 
@@ -468,13 +467,13 @@ args = (sys.stderr,)
 
formatter = generic
 

	
 
[handler_console_color]
 
# ANSI color coding based on log level
 
## ANSI color coding based on log level
 
class = StreamHandler
 
args = (sys.stderr,)
 
formatter = color_formatter
 

	
 
[handler_console_color_sql]
 
# ANSI color coding and pretty printing of SQL statements
 
## ANSI color coding and pretty printing of SQL statements
 
class = StreamHandler
 
args = (sys.stderr,)
 
formatter = color_formatter_sql
 
@@ -505,16 +504,16 @@ datefmt = %Y-%m-%d %H:%M:%S
 
## SSH LOGGING ##
 
#################
 

	
 
# The default loggers use 'handler_console' that uses StreamHandler with
 
# destination 'sys.stderr'. In the context of the SSH server process, these log
 
# messages would be sent to the client, which is normally not what you want.
 
# By default, when running ssh-serve, just use NullHandler and disable logging
 
# completely. For other logging options, see:
 
# https://docs.python.org/2/library/logging.handlers.html
 
## The default loggers use 'handler_console' that uses StreamHandler with
 
## destination 'sys.stderr'. In the context of the SSH server process, these log
 
## messages would be sent to the client, which is normally not what you want.
 
## By default, when running ssh-serve, just use NullHandler and disable logging
 
## completely. For other logging options, see:
 
## https://docs.python.org/2/library/logging.handlers.html
 

	
 
[ssh_serve:logger_root]
 
level = CRITICAL
 
handlers = null
 

	
 
# Note: If logging is configured with other handlers, they might need similar
 
# muting for ssh-serve too.
 
## Note: If logging is configured with other handlers, they might need similar
 
## muting for ssh-serve too.
docs/administrator_guide/auth.rst
Show inline comments
 
@@ -135,10 +135,10 @@ Certificate Checks : optional
 
.. _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.
 
    Directory used by OpenSSL to find CAs for validating the LDAP server
 
    certificate. It defaults to using the system certificate store, and it
 
    should thus not be necessary to specify *Custom CA Certificates* 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.
 

	
docs/api/models.rst
Show inline comments
 
@@ -13,9 +13,6 @@ The :mod:`models` module
 
.. automodule:: kallithea.model.permission
 
   :members:
 

	
 
.. automodule:: kallithea.model.repo_permission
 
   :members:
 

	
 
.. automodule:: kallithea.model.repo
 
   :members:
 

	
docs/conf.py
Show inline comments
 
@@ -46,8 +46,8 @@ source_suffix = '.rst'
 
master_doc = 'index'
 

	
 
# General information about the project.
 
project = u'Kallithea'
 
copyright = u'2010-2020 by various authors, licensed as GPLv3.'
 
project = 'Kallithea'
 
copyright = '2010-2020 by various authors, licensed as GPLv3.'
 

	
 
# The version info for the project you're documenting, acts as replacement for
 
# |version| and |release|, also used in various other places throughout the
 
@@ -187,8 +187,8 @@ htmlhelp_basename = 'Kallithea-docs'
 
# Grouping the document tree into LaTeX files. List of tuples
 
# (source start file, target name, title, author, documentclass [howto/manual]).
 
latex_documents = [
 
  ('index', 'Kallithea.tex', u'Kallithea Documentation',
 
   u'Kallithea Developers', 'manual'),
 
  ('index', 'Kallithea.tex', 'Kallithea Documentation',
 
   'Kallithea Developers', 'manual'),
 
]
 

	
 
# The name of an image file (relative to this directory) to place at the top of
 
@@ -220,8 +220,8 @@ latex_documents = [
 
# One entry per manual page. List of tuples
 
# (source start file, name, description, authors, manual section).
 
man_pages = [
 
    ('index', 'kallithea', u'Kallithea Documentation',
 
     [u'Kallithea Developers'], 1)
 
    ('index', 'kallithea', 'Kallithea Documentation',
 
     ['Kallithea Developers'], 1)
 
]
 

	
 

	
docs/contributing.rst
Show inline comments
 
@@ -32,7 +32,7 @@ To get started with Kallithea developmen
 

	
 
        hg clone https://kallithea-scm.org/repos/kallithea
 
        cd kallithea
 
        virtualenv ../kallithea-venv
 
        python3 -m venv ../kallithea-venv
 
        source ../kallithea-venv/bin/activate
 
        pip install --upgrade pip setuptools
 
        pip install --upgrade -e . -r dev_requirements.txt python-ldap python-pam
 
@@ -92,8 +92,7 @@ Note that on unix systems, the temporary
 
and the test suite creates repositories in the temporary directory. Linux
 
systems with /tmp mounted noexec will thus fail.
 

	
 
You can also use ``tox`` to run the tests with all supported Python versions
 
(currently only Python 2.7).
 
You can also use ``tox`` to run the tests with all supported Python versions.
 

	
 
When running tests, Kallithea generates a `test.ini` based on template values
 
in `kallithea/tests/conftest.py` and populates the SQLite database specified
 
@@ -199,8 +198,7 @@ of Mercurial's (https://www.mercurial-sc
 
consistency with existing code. Run ``scripts/run-all-cleanup`` before
 
committing to ensure some basic code formatting consistency.
 

	
 
We currently only support Python 2.7.x and nothing else. For now we don't care
 
about Python 3 compatibility.
 
We support Python 3.6 and later.
 

	
 
We try to support the most common modern web browsers. IE9 is still supported
 
to the extent it is feasible, IE8 is not.
 
@@ -238,8 +236,8 @@ Each HTTP request runs inside an indepen
 
as in an independent database transaction). ``Session`` is the session manager
 
and factory. ``Session()`` will create a new session on-demand or return the
 
current session for the active thread. Many database operations are methods on
 
such session instances - only ``Session.remove()`` should be called directly on
 
the manager.
 
such session instances. The session will generally be removed by
 
TurboGears automatically.
 

	
 
Database model objects
 
(almost) always belong to a particular SQLAlchemy session, which means
 
@@ -268,6 +266,20 @@ code needs the database to assign an "au
 
a freshly created model object (before flushing, the ID attribute will
 
be ``None``).
 

	
 
Debugging
 
^^^^^^^^^
 

	
 
A good way to trace what Kallithea is doing is to keep an eye on the output of
 
stdout/stderr from the server process. Perhaps change ``my.ini`` to log at
 
``DEBUG`` or ``INFO`` level, especially ``[logger_kallithea]``, but perhaps
 
also other loggers. It is often easier to add additional ``log`` or ``print``
 
statements than to use a Python debugger.
 

	
 
Sometimes it is simpler to disable ``errorpage.enabled`` and perhaps also
 
``trace_errors.enable`` to expose raw errors instead of adding extra
 
processing. Enabling ``debug`` can be helpful for showing and exploring
 
tracebacks in the browser, but is also insecure and will add extra processing.
 

	
 
TurboGears2 DebugBar
 
^^^^^^^^^^^^^^^^^^^^
 

	
docs/index.rst
Show inline comments
 
@@ -78,7 +78,6 @@ Developer guide
 
   dev/dbmigrations
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _python: http://www.python.org/
 
.. _django: http://www.djangoproject.com/
 
.. _mercurial: https://www.mercurial-scm.org/
docs/installation.rst
Show inline comments
 
@@ -35,12 +35,12 @@ Git and development packages for the dat
 
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
 
    sudo apt-get install build-essential git libffi-dev python3-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
 
    sudo yum install gcc git libffi-devel python3-devel
 

	
 
.. _installation-source:
 

	
 
@@ -48,16 +48,16 @@ reasonable set of dependencies is instal
 
Installation from repository source
 
-----------------------------------
 

	
 
To install Kallithea in a virtualenv_ using the stable branch of the development
 
To install Kallithea in a virtualenv using the stable branch of the development
 
repository, follow the instructions below::
 

	
 
        hg clone https://kallithea-scm.org/repos/kallithea -u stable
 
        cd kallithea
 
        virtualenv ../kallithea-venv
 
        python3 -m venv ../kallithea-venv
 
        . ../kallithea-venv/bin/activate
 
        pip install --upgrade pip setuptools
 
        pip install --upgrade -e .
 
        python2 setup.py compile_catalog   # for translation of the UI
 
        python3 setup.py compile_catalog   # for translation of the UI
 

	
 
You can now proceed to :ref:`setup`.
 

	
 
@@ -67,18 +67,18 @@ You can now proceed to :ref:`setup`.
 
Installing a released version in a virtualenv
 
---------------------------------------------
 

	
 
It is highly recommended to use a separate virtualenv_ for installing Kallithea.
 
It is highly recommended to use a separate virtualenv for installing Kallithea.
 
This way, all libraries required by Kallithea will be installed separately from your
 
main Python installation and other applications and things will be less
 
problematic when upgrading the system or Kallithea.
 
An additional benefit of virtualenv_ is that it doesn't require root privileges.
 
An additional benefit of virtualenv is that it doesn't require root privileges.
 

	
 
- Assuming you have installed virtualenv_, create a new virtual environment
 
  for example, in `/srv/kallithea/venv`, using the virtualenv command::
 
- Assuming you have installed virtualenv, create a new virtual environment
 
  for example, in `/srv/kallithea/venv`, using the venv command::
 

	
 
    virtualenv /srv/kallithea/venv
 
    python3 -m venv /srv/kallithea/venv
 

	
 
- Activate the virtualenv_ in your current shell session and make sure the
 
- Activate the virtualenv in your current shell session and make sure the
 
  basic requirements are up-to-date by running::
 

	
 
    . /srv/kallithea/venv/bin/activate
 
@@ -133,6 +133,3 @@ To install as a regular user in ``~/.loc
 
    pip install --user kallithea
 

	
 
You can now proceed to :ref:`setup`.
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
docs/installation_iis.rst
Show inline comments
 
.. _installation_iis:
 

	
 
.. warning:: This section is outdated and needs updating for Python 3.
 

	
 
=====================================================================
 
Installing Kallithea on Microsoft Internet Information Services (IIS)
 
=====================================================================
 
@@ -66,7 +68,7 @@ the necessary components to finalize an 
 
has been generated, it is necessary to run the following command due to the way
 
that ISAPI-WSGI is made::
 

	
 
    python2 dispatch.py install
 
    python3 dispatch.py install
 

	
 
This accomplishes two things: generating an ISAPI compliant DLL file,
 
``_dispatch.dll``, and installing a script map handler into IIS for the
 
@@ -119,7 +121,7 @@ ISAPI-WSGI wrapper above uses ``win32tra
 
In order to dump output from WSGI using ``win32traceutil`` it is sufficient to
 
type the following in a console window::
 

	
 
    python2 -m win32traceutil
 
    python3 -m win32traceutil
 

	
 
and any exceptions occurring in the WSGI layer and below (i.e. in the Kallithea
 
application itself) that are uncaught, will be printed here complete with stack
docs/installation_win.rst
Show inline comments
 
.. _installation_win:
 

	
 
.. warning:: This section is outdated and needs updating for Python 3.
 

	
 
====================================================
 
Installation on Windows (7/Server 2008 R2 and newer)
 
====================================================
 
@@ -17,18 +19,16 @@ To install on an older version of Window
 
Step 1 -- Install Python
 
^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Install Python 2.7.x. Latest version is recommended. If you need another version, they can run side by side.
 
Install Python 3. Latest version is recommended. If you need another version, they can run side by side.
 

	
 
.. warning:: Python 3.x is not supported.
 

	
 
- Download Python 2.7.x from http://www.python.org/download/
 
- Download Python 3 from http://www.python.org/download/
 
- Choose and click on the version
 
- Click on "Windows X86-64 Installer" for x64 or "Windows x86 MSI installer" for Win32.
 
- Disable UAC or run the installer with admin privileges. If you chose to disable UAC, do not forget to reboot afterwards.
 

	
 
While writing this guide, the latest version was v2.7.9.
 
While writing this guide, the latest version was v3.8.1.
 
Remember the specific major and minor versions installed, because they will
 
be needed in the next step. In this case, it is "2.7".
 
be needed in the next step. In this case, it is "3.8".
 

	
 
Step 2 -- Python BIN
 
^^^^^^^^^^^^^^^^^^^^
 
@@ -42,7 +42,7 @@ Open a CMD and type::
 
  SETX PATH "%PATH%;[your-python-path]" /M
 

	
 
Please substitute [your-python-path] with your Python installation
 
path. Typically this is ``C:\\Python27``.
 
path. Typically this is ``C:\\Python38``.
 

	
 
Step 3 -- Install pywin32 extensions
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -52,38 +52,14 @@ http://sourceforge.net/projects/pywin32/
 

	
 
- Click on "pywin32" folder
 
- Click on the first folder (in this case, Build 219, maybe newer when you try)
 
- Choose the file ending with ".amd64-py2.x.exe" (".win32-py2.x.exe"
 
- Choose the file ending with ".amd64-py3.x.exe" (".win32-py3.x.exe"
 
  for Win32) where x is the minor version of Python you installed.
 
  When writing this guide, the file was:
 
  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win-amd64-py2.7.exe/download
 
  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win-amd64-py3.8.exe/download
 
  (x64)
 
  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win32-py2.7.exe/download
 
  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win32-py3.8.exe/download
 
  (Win32)
 

	
 
Step 4 -- Install pip
 
^^^^^^^^^^^^^^^^^^^^^
 

	
 
pip is a package management system for Python. You will need it to install Kallithea and its dependencies.
 

	
 
If you installed Python 2.7.9+, you already have it (as long as you ran the installer with admin privileges or disabled UAC).
 

	
 
If it was not installed or if you are using Python < 2.7.9:
 

	
 
- Go to https://bootstrap.pypa.io
 
- Right-click on get-pip.py and choose Saves as...
 
- Run "python2 get-pip.py" in the folder where you downloaded get-pip.py (may require admin access).
 

	
 
.. note::
 

	
 
   See http://stackoverflow.com/questions/4750806/how-to-install-pip-on-windows
 
   for details and alternative methods.
 

	
 
Note that pip.exe will be placed inside your Python installation's
 
Scripts folder, which is likely not on your path. To correct this,
 
open a CMD and type::
 

	
 
  SETX PATH "%PATH%;[your-python-path]\Scripts" /M
 

	
 
Step 5 -- Kallithea folder structure
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
@@ -108,24 +84,18 @@ Step 6 -- Install virtualenv
 
   A python virtual environment will allow for isolation between the Python packages of your system and those used for Kallithea.
 
   It is strongly recommended to use it to ensure that Kallithea does not change a dependency that other software uses or vice versa.
 

	
 
In a command prompt type::
 

	
 
  pip install virtualenv
 

	
 
Virtualenv will now be inside your Python Scripts path (C:\\Python27\\Scripts or similar).
 

	
 
To create a virtual environment, run::
 

	
 
  virtualenv C:\Kallithea\Env
 
  python3 -m venv 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.
 
This can be done on Linux without any special steps. On Windows, you will need to install Microsoft Visual C++ compiler for Python 2.7.
 
This can be done on Linux without any special steps. On Windows, you will need to install Microsoft Visual C++ compiler for Python 3.8.
 

	
 
Download and install "Microsoft Visual C++ Compiler for Python 2.7" from http://aka.ms/vcpython27
 
Download and install "Microsoft Visual C++ Compiler for Python 3.8" from http://aka.ms/vcpython27
 

	
 
.. note::
 
  You can also install the dependencies using already compiled Windows binaries packages. A good source of compiled Python packages is http://www.lfd.uci.edu/~gohlke/pythonlibs/. However, not all of the necessary packages for Kallithea are on this site and some are hard to find, so we will stick with using the compiler.
docs/installation_win_old.rst
Show inline comments
 
.. _installation_win_old:
 

	
 
.. warning:: This section is outdated and needs updating for Python 3.
 

	
 
==========================================================
 
Installation on Windows (XP/Vista/Server 2003/Server 2008)
 
==========================================================
 
@@ -60,14 +62,11 @@ choose "Visual C++ 2008 Express" when in
 
Step 2 -- Install Python
 
^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Install Python 2.7.x x86 version (32-bit). DO NOT USE A 3.x version.
 
Download Python 2.7.x from:
 
Install Python 3.8.x from:
 
http://www.python.org/download/
 

	
 
Choose "Windows Installer" (32-bit version) not "Windows X86-64
 
Installer". While writing this guide, the latest version was v2.7.3.
 
Remember the specific major and minor version installed, because it will
 
be needed in the next step. In this case, it is "2.7".
 
be needed in the next step. In this case, it is "3.8".
 

	
 
.. note::
 

	
 
@@ -80,17 +79,17 @@ Download pywin32 from:
 
http://sourceforge.net/projects/pywin32/files/
 

	
 
- Click on "pywin32" folder
 
- Click on the first folder (in this case, Build 217, maybe newer when you try)
 
- Choose the file ending with ".win32-py2.x.exe" -> x being the minor
 
- Click on the first folder (in this case, Build 218, maybe newer when you try)
 
- Choose the file ending with ".win32-py3.x.exe" -> x being the minor
 
  version of Python you installed (in this case, 7)
 
  When writing this guide, the file was:
 
  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/pywin32-217.win32-py2.7.exe/download
 
  http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py3.8.exe/download
 

	
 
  .. note::
 

	
 
     64-bit: Download and install the 64-bit version.
 
     At the time of writing you can find this at:
 
     http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download
 
     http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py3.8.exe/download
 

	
 
Step 4 -- Python BIN
 
^^^^^^^^^^^^^^^^^^^^
 
@@ -117,7 +116,7 @@ that came preinstalled in Vista/7 and ca
 
    SETX PATH "%PATH%;[your-python-path]" /M
 

	
 
  Please substitute [your-python-path] with your Python installation path.
 
  Typically: C:\\Python27
 
  Typically: C:\\Python38
 

	
 
Step 5 -- Kallithea folder structure
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -139,22 +138,10 @@ Create the following folder structure::
 
Step 6 -- Install virtualenv
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Install Virtual Env for Python
 

	
 
Navigate to: http://www.virtualenv.org/en/latest/index.html#installation
 
Right click on "virtualenv.py" file and choose "Save link as...".
 
Download to C:\\Kallithea (or whatever you want)
 
(the file is located at
 
https://raw.github.com/pypa/virtualenv/master/virtualenv.py)
 
Create a virtual Python environment in C:\\Kallithea\\Env (or similar). To
 
do so, open a CMD (Python Path should be included in Step3), and write::
 

	
 
Create a virtual Python environment in C:\\Kallithea\\Env (or similar). To
 
do so, open a CMD (Python Path should be included in Step3), navigate
 
where you downloaded "virtualenv.py", and write::
 

	
 
  python2 virtualenv.py C:\Kallithea\Env
 

	
 
(--no-site-packages is now the default behaviour of virtualenv, no need
 
to include it)
 
  python3 -m venv C:\Kallithea\Env
 

	
 
Step 7 -- Install Kallithea
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^
docs/overview.rst
Show inline comments
 
@@ -12,7 +12,7 @@ Python environment
 
------------------
 

	
 
**Kallithea** is written entirely in Python_ and requires Python version
 
2.7 or higher. Python 3.x is currently not supported.
 
3.6 or higher.
 

	
 
Given a Python installation, there are different ways of providing the
 
environment for running Python applications. Each of them pretty much
 
@@ -30,7 +30,7 @@ environment used for running Kallithea.
 
- Packages could also be installed in ``~/.local`` ... but that is probably
 
  only a good idea if using a dedicated user per application or instance.
 

	
 
- Finally, it can be installed in a virtualenv_. That is a very lightweight
 
- Finally, it can be installed in a virtualenv. That is a very lightweight
 
  "container" where each Kallithea instance can get its own dedicated and
 
  self-contained virtual environment.
 

	
 
@@ -98,7 +98,7 @@ installed.
 
  installed with all dependencies using ``pip install kallithea``.
 

	
 
  With this method, Kallithea is installed in the Python environment as any
 
  other package, usually as a ``.../site-packages/Kallithea-X-py2.7.egg/``
 
  other package, usually as a ``.../site-packages/Kallithea-X-py3.8.egg/``
 
  directory with Python files and everything else that is needed.
 

	
 
  (``pip install kallithea`` from a source tree will do pretty much the same
 
@@ -165,7 +165,6 @@ continuous hammering from the internet.
 
.. _Python: http://www.python.org/
 
.. _Gunicorn: http://gunicorn.org/
 
.. _Waitress: http://waitress.readthedocs.org/en/latest/
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _Gearbox: http://turbogears.readthedocs.io/en/latest/turbogears/gearbox.html
 
.. _PyPI: https://pypi.python.org/pypi
 
.. _Apache httpd: http://httpd.apache.org/
docs/setup.rst
Show inline comments
 
@@ -332,11 +332,11 @@ To enable it, simply set::
 

	
 
  use_celery = true
 

	
 
and add or change the ``celery.*`` and ``broker.*`` configuration variables.
 
and add or change the ``celery.*`` configuration variables.
 

	
 
Remember that the ini files use the format with '.' and not with '_' like
 
Celery. So for example setting `BROKER_HOST` in Celery means setting
 
`broker.host` in the configuration file.
 
Configuration settings are prefixed with 'celery.', so for example setting
 
`broker_url` in Celery means setting `celery.broker_url` in the configuration
 
file.
 

	
 
To start the Celery process, run::
 

	
 
@@ -557,7 +557,7 @@ that, you'll need to:
 
      os.chdir('/srv/kallithea/')
 

	
 
      import site
 
      site.addsitedir("/srv/kallithea/venv/lib/python2.7/site-packages")
 
      site.addsitedir("/srv/kallithea/venv/lib/python3.7/site-packages")
 

	
 
      ini = '/srv/kallithea/my.ini'
 
      from logging.config import fileConfig
 
@@ -624,7 +624,6 @@ the ``init.d`` directory of the Kallithe
 
.. __: https://kallithea-scm.org/repos/kallithea/files/tip/init.d/ .
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _python: http://www.python.org/
 
.. _Python regular expression documentation: https://docs.python.org/2/library/re.html
 
.. _Mercurial: https://www.mercurial-scm.org/
docs/upgrade.rst
Show inline comments
 
@@ -241,6 +241,3 @@ To update the hooks of your Git reposito
 
.. note::
 
    Kallithea does not use hooks on Mercurial repositories. This step is thus
 
    not necessary if you only have Mercurial repositories.
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
docs/usage/troubleshooting.rst
Show inline comments
 
@@ -8,7 +8,7 @@ Troubleshooting
 
:A: Make sure either to set the ``static_files = true`` in the .ini file or
 
   double check the root path for your http setup. It should point to
 
   for example:
 
   ``/home/my-virtual-python/lib/python2.7/site-packages/kallithea/public``
 
   ``/home/my-virtual-python/lib/python3.7/site-packages/kallithea/public``
 

	
 
|
 

	
 
@@ -67,7 +67,6 @@ Troubleshooting
 
    you have installed the latest Windows patches (especially KB2789397).
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _python: http://www.python.org/
 
.. _mercurial: https://www.mercurial-scm.org/
 
.. _celery: http://celeryproject.org/
kallithea/__init__.py
Show inline comments
 
@@ -31,13 +31,16 @@ import platform
 
import sys
 

	
 

	
 
VERSION = (0, 5, 2)
 
if sys.version_info < (3, 6):
 
    raise Exception('Kallithea requires python 3.6 or later')
 

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

	
 
CELERY_ON = False
 
CELERY_APP = None  # set to Celery app instance if using Celery
 
CELERY_EAGER = False
 

	
 
CONFIG = {}
kallithea/alembic/versions/a0a1bf09c143_db_add_ui_composite_index_and_drop_.py
Show inline comments
 
new file 100644
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
"""db: add Ui composite index and drop UniqueConstraint on Ui.ui_key
 

	
 
Revision ID: a0a1bf09c143
 
Revises: d7ec25b66e47
 
Create Date: 2020-03-12 22:41:14.421837
 

	
 
"""
 

	
 
# The following opaque hexadecimal identifiers ("revisions") are used
 
# by Alembic to track this migration script and its relations to others.
 
revision = 'a0a1bf09c143'
 
down_revision = 'd7ec25b66e47'
 
branch_labels = None
 
depends_on = None
 

	
 
import sqlalchemy as sa
 
from alembic import op
 

	
 

	
 
def upgrade():
 
    meta = sa.MetaData()
 
    meta.reflect(bind=op.get_bind())
 

	
 
    with op.batch_alter_table('ui', schema=None) as batch_op:
 
        batch_op.create_index('ui_ui_section_ui_key_idx', ['ui_section', 'ui_key'], unique=False)
 
        if any(i.name == 'uq_ui_ui_key' for i in meta.tables['ui'].constraints):
 
            batch_op.drop_constraint('uq_ui_ui_key', type_='unique')
 
        elif any(i.name == 'ui_ui_key_key' for i in meta.tables['ui'].constraints):  # table was created with old naming before 1a080d4e926e
 
            batch_op.drop_constraint('ui_ui_key_key', type_='unique')
 

	
 

	
 
def downgrade():
 
    with op.batch_alter_table('ui', schema=None) as batch_op:
 
        batch_op.create_unique_constraint('uq_ui_ui_key', ['ui_key'])
 
        batch_op.drop_index('ui_ui_section_ui_key_idx')
kallithea/bin/base.py
Show inline comments
 
@@ -29,9 +29,10 @@ import os
 
import pprint
 
import random
 
import sys
 
import urllib2
 
import urllib.request
 

	
 
from kallithea.lib.compat import json
 
from kallithea.lib import ext_json
 
from kallithea.lib.utils2 import ascii_bytes
 

	
 

	
 
CONFIG_NAME = '.config/kallithea'
 
@@ -67,12 +68,12 @@ def api_call(apikey, apihost, method=Non
 
        raise Exception('please specify method name !')
 
    apihost = apihost.rstrip('/')
 
    id_ = random.randrange(1, 9999)
 
    req = urllib2.Request('%s/_admin/api' % apihost,
 
                      data=json.dumps(_build_data(id_)),
 
    req = urllib.request.Request('%s/_admin/api' % apihost,
 
                      data=ascii_bytes(ext_json.dumps(_build_data(id_))),
 
                      headers={'content-type': 'text/plain'})
 
    ret = urllib2.urlopen(req)
 
    ret = urllib.request.urlopen(req)
 
    raw_json = ret.read()
 
    json_data = json.loads(raw_json)
 
    json_data = ext_json.loads(raw_json)
 
    id_ret = json_data['id']
 
    if id_ret == id_:
 
        return json_data
 
@@ -107,7 +108,7 @@ class RcConf(object):
 
    def __getitem__(self, key):
 
        return self._conf[key]
 

	
 
    def __nonzero__(self):
 
    def __bool__(self):
 
        if self._conf:
 
            return True
 
        return False
 
@@ -128,7 +129,7 @@ class RcConf(object):
 
        if os.path.exists(self._conf_name):
 
            update = True
 
        with open(self._conf_name, 'wb') as f:
 
            json.dump(config, f, indent=4)
 
            ext_json.dump(config, f, indent=4)
 
            f.write('\n')
 

	
 
        if update:
 
@@ -146,7 +147,7 @@ class RcConf(object):
 
        config = {}
 
        try:
 
            with open(self._conf_name, 'rb') as conf:
 
                config = json.load(conf)
 
                config = ext_json.load(conf)
 
        except IOError as e:
 
            sys.stderr.write(str(e) + '\n')
 

	
 
@@ -159,7 +160,7 @@ class RcConf(object):
 
        """
 
        try:
 
            with open(self._conf_name, 'rb') as conf:
 
                return json.load(conf)
 
                return ext_json.load(conf)
 
        except IOError as e:
 
            #sys.stderr.write(str(e) + '\n')
 
            pass
kallithea/bin/kallithea_api.py
Show inline comments
 
@@ -25,12 +25,11 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from __future__ import print_function
 

	
 
import argparse
 
import json
 
import sys
 

	
 
from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call, json
 
from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call
 

	
 

	
 
def argparser(argv):
 
@@ -60,7 +59,7 @@ def argparser(argv):
 
                 'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON),
 
            default=FORMAT_PRETTY
 
    )
 
    args, other = parser.parse_known_args()
 
    args, other = parser.parse_known_args(args=argv[1:])
 
    return parser, args, other
 

	
 

	
 
@@ -101,7 +100,7 @@ def main(argv=None):
 
        parser.error('Please specify method name')
 

	
 
    try:
 
        margs = dict(map(lambda s: s.split(':', 1), other))
 
        margs = dict(s.split(':', 1) for s in other)
 
    except ValueError:
 
        sys.stderr.write('Error parsing arguments \n')
 
        sys.exit()
kallithea/bin/kallithea_cli.py
Show inline comments
 
@@ -25,3 +25,8 @@ import kallithea.bin.kallithea_cli_repo
 
import kallithea.bin.kallithea_cli_ssh
 
# 'cli' is the main entry point for 'kallithea-cli', specified in setup.py as entry_points console_scripts
 
from kallithea.bin.kallithea_cli_base import cli
 

	
 

	
 
# mute pyflakes "imported but unused"
 
assert kallithea.bin.kallithea_cli_ssh
 
assert cli
kallithea/bin/kallithea_cli_base.py
Show inline comments
 
@@ -12,7 +12,7 @@
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import cStringIO
 
import configparser
 
import functools
 
import logging.config
 
import os
 
@@ -23,6 +23,7 @@ import click
 
import paste.deploy
 

	
 
import kallithea
 
import kallithea.config.middleware
 

	
 

	
 
# kallithea_cli is usually invoked through the 'kallithea-cli' wrapper script
 
@@ -71,12 +72,12 @@ def register_command(config_file=False, 
 
            def runtime_wrapper(config_file, *args, **kwargs):
 
                path_to_ini_file = os.path.realpath(config_file)
 
                kallithea.CONFIG = paste.deploy.appconfig('config:' + path_to_ini_file)
 
                config_bytes = read_config(path_to_ini_file, strip_section_prefix=annotated.__name__)
 
                logging.config.fileConfig(cStringIO.StringIO(config_bytes),
 
                cp = configparser.ConfigParser(strict=False)
 
                cp.read_string(read_config(path_to_ini_file, strip_section_prefix=annotated.__name__))
 
                logging.config.fileConfig(cp,
 
                    {'__file__': path_to_ini_file, 'here': os.path.dirname(path_to_ini_file)})
 
                if config_file_initialize_app:
 
                    kallithea.config.middleware.make_app_without_logging(kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
 
                    kallithea.lib.utils.setup_cache_regions(kallithea.CONFIG)
 
                    kallithea.config.middleware.make_app(kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
 
                return annotated(*args, **kwargs)
 
            return cli_command(runtime_wrapper)
 
        return annotator
kallithea/bin/kallithea_cli_celery.py
Show inline comments
 
@@ -12,6 +12,7 @@
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import celery.bin.worker
 
import click
 

	
 
import kallithea
 
@@ -31,10 +32,9 @@ def celery_run(celery_args):
 
    by this CLI command.
 
    """
 

	
 
    if not kallithea.CELERY_ON:
 
    if not kallithea.CELERY_APP:
 
        raise Exception('Please set use_celery = true in .ini config '
 
                        'file before running this command')
 

	
 
    from kallithea.lib import celerypylons
 
    cmd = celerypylons.worker.worker(celerypylons.app)
 
    cmd = celery.bin.worker.worker(kallithea.CELERY_APP)
 
    return cmd.run_from_argv(None, command='celery-run -c CONFIG_FILE --', argv=list(celery_args))
kallithea/bin/kallithea_cli_config.py
Show inline comments
 
@@ -89,6 +89,16 @@ def config_create(config_file, key_value
 
    mako_variable_values.update({
 
        'uuid': lambda: uuid.uuid4().hex,
 
    })
 

	
 
    click.echo('Creating config file using:')
 
    for key, value in inifile.default_variables.items():
 
        if isinstance(value, str):
 
            options = inifile.variable_options.get(key)
 
            if options:
 
                click.echo('  %s=%s  (options: %s)' % (key, mako_variable_values.get(key, value), ', '.join(options)))
 
            else:
 
                click.echo('  %s=%s' % (key, mako_variable_values.get(key, value)))
 

	
 
    try:
 
        config_file_abs = os.path.abspath(config_file)
 
        inifile.create(config_file_abs, mako_variable_values, ini_settings)
kallithea/bin/kallithea_cli_db.py
Show inline comments
 
@@ -67,7 +67,7 @@ def db_create(user, password, email, rep
 
    Session().commit()
 

	
 
    # initial repository scan
 
    kallithea.config.middleware.make_app_without_logging(
 
    kallithea.config.middleware.make_app(
 
            kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
 
    added, _ = kallithea.lib.utils.repo2db_mapper(kallithea.model.scm.ScmModel().repo_scan())
 
    if added:
kallithea/bin/kallithea_cli_iis.py
Show inline comments
 
@@ -67,6 +67,7 @@ def iis_install(virtualdir):
 

	
 
    try:
 
        import isapi_wsgi
 
        assert isapi_wsgi
 
    except ImportError:
 
        sys.stderr.write('missing requirement: isapi-wsgi not installed\n')
 
        sys.exit(1)
kallithea/bin/kallithea_cli_index.py
Show inline comments
 
@@ -36,7 +36,7 @@ from kallithea.model.repo import RepoMod
 
@click.option('--repo-location', help='Base path of repositories to index. Default: all')
 
@click.option('--index-only', help='Comma-separated list of repositories to build index on. Default: all')
 
@click.option('--update-only', help='Comma-separated list of repositories to re-build index on. Default: all')
 
@click.option('-f', '--full', 'full_index', help='Recreate the index from scratch')
 
@click.option('-f', '--full/--no-full', 'full_index', help='Recreate the index from scratch')
 
def index_create(repo_location, index_only, update_only, full_index):
 
    """Create or update full text search index"""
 

	
kallithea/bin/kallithea_cli_ishell.py
Show inline comments
 
@@ -20,12 +20,10 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from __future__ import print_function
 

	
 
import sys
 

	
 
import kallithea.bin.kallithea_cli_base as cli_base
 
from kallithea.model.db import *
 
from kallithea.model.db import *  # these names will be directly available in the IPython shell
 

	
 

	
 
@cli_base.register_command(config_file_initialize_app=True)
kallithea/bin/kallithea_cli_repo.py
Show inline comments
 
@@ -26,10 +26,11 @@ import shutil
 

	
 
import click
 

	
 
import kallithea
 
import kallithea.bin.kallithea_cli_base as cli_base
 
from kallithea.lib.utils import REMOVED_REPO_PAT, repo2db_mapper
 
from kallithea.lib.utils2 import ask_ok, safe_str, safe_unicode
 
from kallithea.model.db import Repository, Ui
 
from kallithea.lib.utils2 import ask_ok
 
from kallithea.model.db import Repository
 
from kallithea.model.meta import Session
 
from kallithea.model.scm import ScmModel
 

	
 
@@ -74,7 +75,7 @@ def repo_update_metadata(repositories):
 
    if not repositories:
 
        repo_list = Repository.query().all()
 
    else:
 
        repo_names = [safe_unicode(n.strip()) for n in repositories]
 
        repo_names = [n.strip() for n in repositories]
 
        repo_list = list(Repository.query()
 
                        .filter(Repository.repo_name.in_(repo_names)))
 

	
 
@@ -110,7 +111,7 @@ def repo_purge_deleted(ask, older_than):
 
            return
 
        parts = parts.groupdict()
 
        time_params = {}
 
        for (name, param) in parts.iteritems():
 
        for name, param in parts.items():
 
            if param:
 
                time_params[name] = int(param)
 
        return datetime.timedelta(**time_params)
 
@@ -125,9 +126,9 @@ def repo_purge_deleted(ask, older_than):
 
        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')
 

	
 
    repos_location = Ui.get_repos_location()
 
    repos_location = kallithea.CONFIG['base_path']
 
    to_remove = []
 
    for dn_, dirs, f in os.walk(safe_str(repos_location)):
 
    for dn_, dirs, f in os.walk(repos_location):
 
        alldirs = list(dirs)
 
        del dirs[:]
 
        if ('.hg' in alldirs or
 
@@ -175,9 +176,8 @@ def repo_purge_deleted(ask, older_than):
 
        remove = True
 
    else:
 
        remove = ask_ok('The following repositories will be removed completely:\n%s\n'
 
                'Do you want to proceed? [y/n] '
 
                % '\n'.join(['%s deleted on %s' % (safe_str(x[0]), safe_str(x[1]))
 
                                     for x in to_remove]))
 
            'Do you want to proceed? [y/n] ' %
 
            '\n'.join('%s deleted on %s' % (path, date_) for path, date_ in to_remove))
 

	
 
    if remove:
 
        for path, date_ in to_remove:
kallithea/bin/kallithea_cli_ssh.py
Show inline comments
 
@@ -14,7 +14,6 @@
 

	
 
import logging
 
import os
 
import re
 
import shlex
 
import sys
 

	
kallithea/bin/kallithea_gist.py
Show inline comments
 
@@ -25,15 +25,14 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from __future__ import print_function
 

	
 
import argparse
 
import fileinput
 
import json
 
import os
 
import stat
 
import sys
 

	
 
from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call, json
 
from kallithea.bin.base import FORMAT_JSON, FORMAT_PRETTY, RcConf, api_call
 

	
 

	
 
def argparser(argv):
 
@@ -69,7 +68,7 @@ def argparser(argv):
 
                       'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON),
 
            default=FORMAT_PRETTY
 
    )
 
    args, other = parser.parse_known_args()
 
    args, other = parser.parse_known_args(args=argv[1:])
 
    return parser, args, other
 

	
 

	
kallithea/bin/ldap_sync.py
Show inline comments
 
@@ -25,15 +25,14 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
from __future__ import print_function
 

	
 
import urllib2
 
import urllib.request
 
import uuid
 
from ConfigParser import ConfigParser
 
from configparser import ConfigParser
 

	
 
import ldap
 

	
 
from kallithea.lib.compat import json
 
from kallithea.lib import ext_json
 
from kallithea.lib.utils2 import ascii_bytes
 

	
 

	
 
config = ConfigParser()
 
@@ -80,12 +79,12 @@ class API(object):
 
        uid = str(uuid.uuid1())
 
        data = self.get_api_data(uid, method, args)
 

	
 
        data = json.dumps(data)
 
        data = ascii_bytes(ext_json.dumps(data))
 
        headers = {'content-type': 'text/plain'}
 
        req = urllib2.Request(self.url, data, headers)
 
        req = urllib.request.Request(self.url, data, headers)
 

	
 
        response = urllib2.urlopen(req)
 
        response = json.load(response)
 
        response = urllib.request.urlopen(req)
 
        response = ext_json.load(response)
 

	
 
        if uid != response["id"]:
 
            raise InvalidResponseIDError("UUID does not match.")
kallithea/config/app_cfg.py
Show inline comments
 
@@ -28,20 +28,21 @@ import tg
 
from alembic.migration import MigrationContext
 
from alembic.script.base import ScriptDirectory
 
from sqlalchemy import create_engine
 
from tg import hooks
 
from tg.configuration import AppConfig
 
from tg.support.converters import asbool
 

	
 
import kallithea.lib.locale
 
import kallithea.model.base
 
from kallithea.lib.auth import set_available_permissions
 
import kallithea.model.meta
 
from kallithea.lib import celerypylons
 
from kallithea.lib.middleware.https_fixup import HttpsFixup
 
from kallithea.lib.middleware.permanent_repo_url import PermanentRepoUrl
 
from kallithea.lib.middleware.simplegit import SimpleGit
 
from kallithea.lib.middleware.simplehg import SimpleHg
 
from kallithea.lib.middleware.wrapper import RequestWrapper
 
from kallithea.lib.utils import check_git_version, load_rcextensions, make_ui, set_app_settings, set_indexer_config, set_vcs_config
 
from kallithea.lib.utils import check_git_version, load_rcextensions, set_app_settings, set_indexer_config, set_vcs_config
 
from kallithea.lib.utils2 import str2bool
 
from kallithea.model import db
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -98,17 +99,12 @@ class KallitheaAppConfig(AppConfig):
 
        # Disable transaction manager -- currently Kallithea takes care of transactions itself
 
        self['tm.enabled'] = False
 

	
 
        # Set the i18n source language so TG doesn't search beyond 'en' in Accept-Language.
 
        # Don't force the default here if configuration force something else.
 
        if not self.get('i18n.lang'):
 
        # Set the default i18n source language so TG doesn't search beyond 'en' in Accept-Language.
 
            self['i18n.lang'] = 'en'
 

	
 

	
 
base_config = KallitheaAppConfig()
 

	
 
# TODO still needed as long as we use pylonslib
 
sys.modules['pylons'] = tg
 

	
 
# DebugBar, a debug toolbar for TurboGears2.
 
# (https://github.com/TurboGears/tgext.debugbar)
 
# To enable it, install 'tgext.debugbar' and 'kajiki', and run Kallithea with
 
@@ -117,6 +113,7 @@ sys.modules['pylons'] = tg
 
try:
 
    from tgext.debugbar import enable_debugbar
 
    import kajiki # only to check its existence
 
    assert kajiki
 
except ImportError:
 
    pass
 
else:
 
@@ -161,15 +158,14 @@ def setup_configuration(app):
 
            sys.exit(1)
 

	
 
    # store some globals into kallithea
 
    kallithea.CELERY_ON = str2bool(config.get('use_celery'))
 
    kallithea.CELERY_EAGER = str2bool(config.get('celery.always.eager'))
 
    kallithea.DEFAULT_USER_ID = db.User.get_default_user().user_id
 

	
 
    if str2bool(config.get('use_celery')):
 
        kallithea.CELERY_APP = celerypylons.make_app()
 
    kallithea.CONFIG = config
 

	
 
    load_rcextensions(root_path=config['here'])
 

	
 
    set_available_permissions(config)
 
    repos_path = make_ui().configitems('paths')[0][1]
 
    config['base_path'] = repos_path
 
    set_app_settings(config)
 

	
 
    instance_id = kallithea.CONFIG.get('instance_id', '*')
 
@@ -188,8 +184,10 @@ def setup_configuration(app):
 

	
 
    check_git_version()
 

	
 
    kallithea.model.meta.Session.remove()
 

	
 
hooks.register('configure_new_app', setup_configuration)
 

	
 
tg.hooks.register('configure_new_app', setup_configuration)
 

	
 

	
 
def setup_application(app):
 
@@ -213,4 +211,4 @@ def setup_application(app):
 
    return app
 

	
 

	
 
hooks.register('before_config', setup_application)
 
tg.hooks.register('before_config', setup_application)
kallithea/config/conf.py
Show inline comments
 
@@ -35,7 +35,7 @@ LANGUAGES_EXTENSIONS_MAP = pygmentsutils
 
# Whoosh index targets
 

	
 
# Extensions we want to index content of using whoosh
 
INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
 
INDEX_EXTENSIONS = list(LANGUAGES_EXTENSIONS_MAP)
 

	
 
# Filenames we want to index content of using whoosh
 
INDEX_FILENAMES = pygmentsutils.get_index_filenames()
kallithea/config/middleware.py
Show inline comments
 
@@ -24,11 +24,6 @@ __all__ = ['make_app']
 
make_base_app = base_config.setup_tg_wsgi_app(load_environment)
 

	
 

	
 
def make_app_without_logging(global_conf, full_stack=True, **app_conf):
 
    """The core of make_app for use from gearbox commands (other than 'serve')"""
 
    return make_base_app(global_conf, full_stack=full_stack, **app_conf)
 

	
 

	
 
def make_app(global_conf, full_stack=True, **app_conf):
 
    """
 
    Set up Kallithea with the settings found in the PasteDeploy configuration
 
@@ -47,4 +42,6 @@ def make_app(global_conf, full_stack=Tru
 
    ``app_conf`` contains all the application-specific settings (those defined
 
    under ``[app:main]``.
 
    """
 
    return make_app_without_logging(global_conf, full_stack=full_stack, **app_conf)
 
    assert app_conf.get('sqlalchemy.url')  # must be called with a Kallithea .ini file, which for example must have this config option
 
    assert global_conf.get('here') and global_conf.get('__file__')  # app config should be initialized the paste way ...
 
    return make_base_app(global_conf, full_stack=full_stack, **app_conf)
kallithea/config/routing.py
Show inline comments
 
@@ -19,14 +19,34 @@ may take precedent over the more generic
 
refer to the routes manual at http://routes.groovie.org/docs/
 
"""
 

	
 
from routes import Mapper
 
import routes
 
from tg import request
 

	
 
from kallithea.lib.utils2 import safe_str
 

	
 

	
 
# prefix for non repository related links needs to be prefixed with `/`
 
ADMIN_PREFIX = '/_admin'
 

	
 

	
 
class Mapper(routes.Mapper):
 
    """
 
    Subclassed Mapper with routematch patched to decode "unicode" str url to
 
    *real* unicode str before applying matches and invoking controller methods.
 
    """
 

	
 
    def routematch(self, url=None, environ=None):
 
        """
 
        routematch that also decode url from "fake bytes" to real unicode
 
        string before matching and invoking controllers.
 
        """
 
        # Process url like get_path_info does ... but PATH_INFO has already
 
        # been retrieved from environ and is passed, so - let's just use that
 
        # instead.
 
        url = safe_str(url.encode('latin1'))
 
        return super().routematch(url=url, environ=environ)
 

	
 

	
 
def make_map(config):
 
    """Create, configure and return the routes Mapper"""
 
    rmap = Mapper(directory=config['paths']['controllers'],
 
@@ -86,7 +106,7 @@ def make_map(config):
 
    #==========================================================================
 

	
 
    # MAIN PAGE
 
    rmap.connect('home', '/', controller='home', action='index')
 
    rmap.connect('home', '/', controller='home')
 
    rmap.connect('about', '/about', controller='home', action='about')
 
    rmap.redirect('/favicon.ico', '/images/favicon.ico')
 
    rmap.connect('repo_switcher_data', '/_repos', controller='home',
 
@@ -106,7 +126,7 @@ def make_map(config):
 
        m.connect("repos", "/repos",
 
                  action="create", conditions=dict(method=["POST"]))
 
        m.connect("repos", "/repos",
 
                  action="index", conditions=dict(method=["GET"]))
 
                  conditions=dict(method=["GET"]))
 
        m.connect("new_repo", "/create_repository",
 
                  action="create_repository", conditions=dict(method=["GET"]))
 
        m.connect("update_repo", "/repos/{repo_name:.*?}",
 
@@ -121,7 +141,7 @@ def make_map(config):
 
        m.connect("repos_groups", "/repo_groups",
 
                  action="create", conditions=dict(method=["POST"]))
 
        m.connect("repos_groups", "/repo_groups",
 
                  action="index", conditions=dict(method=["GET"]))
 
                  conditions=dict(method=["GET"]))
 
        m.connect("new_repos_group", "/repo_groups/new",
 
                  action="new", conditions=dict(method=["GET"]))
 
        m.connect("update_repos_group", "/repo_groups/{group_name:.*?}",
 
@@ -161,9 +181,9 @@ def make_map(config):
 
        m.connect("new_user", "/users/new",
 
                  action="create", conditions=dict(method=["POST"]))
 
        m.connect("users", "/users",
 
                  action="index", conditions=dict(method=["GET"]))
 
                  conditions=dict(method=["GET"]))
 
        m.connect("formatted_users", "/users.{format}",
 
                  action="index", conditions=dict(method=["GET"]))
 
                  conditions=dict(method=["GET"]))
 
        m.connect("new_user", "/users/new",
 
                  action="new", conditions=dict(method=["GET"]))
 
        m.connect("update_user", "/users/{id}",
 
@@ -216,7 +236,7 @@ def make_map(config):
 
        m.connect("users_groups", "/user_groups",
 
                  action="create", conditions=dict(method=["POST"]))
 
        m.connect("users_groups", "/user_groups",
 
                  action="index", conditions=dict(method=["GET"]))
 
                  conditions=dict(method=["GET"]))
 
        m.connect("new_users_group", "/user_groups/new",
 
                  action="new", conditions=dict(method=["GET"]))
 
        m.connect("update_users_group", "/user_groups/{id}",
 
@@ -263,8 +283,7 @@ def make_map(config):
 
    # ADMIN DEFAULTS ROUTES
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='admin/defaults') as m:
 
        m.connect('defaults', '/defaults',
 
                  action="index")
 
        m.connect('defaults', '/defaults')
 
        m.connect('defaults_update', 'defaults/{id}/update',
 
                  action="update", conditions=dict(method=["POST"]))
 

	
 
@@ -370,7 +389,7 @@ def make_map(config):
 
        m.connect("gists", "/gists",
 
                  action="create", conditions=dict(method=["POST"]))
 
        m.connect("gists", "/gists",
 
                  action="index", conditions=dict(method=["GET"]))
 
                  conditions=dict(method=["GET"]))
 
        m.connect("new_gist", "/gists/new",
 
                  action="new", conditions=dict(method=["GET"]))
 

	
 
@@ -396,7 +415,7 @@ def make_map(config):
 
    # ADMIN MAIN PAGES
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='admin/admin') as m:
 
        m.connect('admin_home', '', action='index')
 
        m.connect('admin_home', '')
 
        m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9. _-]*}',
 
                  action='add_repo')
 
    #==========================================================================
 
@@ -408,7 +427,7 @@ def make_map(config):
 

	
 
    # USER JOURNAL
 
    rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
 
                 controller='journal', action='index')
 
                 controller='journal')
 
    rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
 
                 controller='journal', action='journal_rss')
 
    rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
 
@@ -475,7 +494,7 @@ def make_map(config):
 
    #==========================================================================
 
    rmap.connect('repo_creating_home', '/{repo_name:.*?}/repo_creating',
 
                controller='admin/repos', action='repo_creating')
 
    rmap.connect('repo_check_home', '/{repo_name:.*?}/crepo_check',
 
    rmap.connect('repo_check_home', '/{repo_name:.*?}/repo_check_creating',
 
                controller='admin/repos', action='repo_check')
 

	
 
    rmap.connect('summary_home', '/{repo_name:.*?}',
 
@@ -544,13 +563,6 @@ def make_map(config):
 
                 controller='admin/repos', action="edit_advanced_fork",
 
                 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("update_repo_caches", "/{repo_name:.*?}/settings/caches",
 
                 controller='admin/repos', action="edit_caches",
 
                 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))
 
@@ -602,7 +614,7 @@ def make_map(config):
 

	
 
    rmap.connect('compare_home',
 
                 '/{repo_name:.*?}/compare',
 
                 controller='compare', action='index',
 
                 controller='compare',
 
                 conditions=dict(function=check_repo))
 

	
 
    rmap.connect('compare_url',
 
@@ -616,7 +628,7 @@ def make_map(config):
 

	
 
    rmap.connect('pullrequest_home',
 
                 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
 
                 action='index', conditions=dict(function=check_repo,
 
                 conditions=dict(function=check_repo,
 
                                                 method=["GET"]))
 

	
 
    rmap.connect('pullrequest_repo_info',
 
@@ -674,7 +686,7 @@ def make_map(config):
 
                controller='changelog', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
 
                controller='changelog', f_path=None,
 
                controller='changelog',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
 
@@ -719,8 +731,8 @@ def make_map(config):
 

	
 
    rmap.connect('files_annotate_home',
 
                 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
 
                 controller='files', action='index', revision='tip',
 
                 f_path='', annotate=True, conditions=dict(function=check_repo))
 
                 controller='files', revision='tip',
 
                 f_path='', annotate='1', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_edit_home',
 
                 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
kallithea/controllers/admin/admin.py
Show inline comments
 
@@ -36,7 +36,6 @@ from whoosh import query
 
from whoosh.qparser.dateparse import DateParserPlugin
 
from whoosh.qparser.default import QueryParser
 

	
 
from kallithea.config.routing import url
 
from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.indexers import JOURNAL_SCHEMA
 
@@ -61,7 +60,7 @@ def _journal_filter(user_log, search_ter
 
    if search_term:
 
        qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
 
        qp.add_plugin(DateParserPlugin())
 
        qry = qp.parse(unicode(search_term))
 
        qry = qp.parse(search_term)
 
        log.debug('Filtering using parsed query %r', qry)
 

	
 
    def wildcard_handler(col, wc_term):
 
@@ -139,10 +138,8 @@ class AdminController(BaseController):
 

	
 
        p = safe_int(request.GET.get('page'), 1)
 

	
 
        def url_generator(**kw):
 
            return url.current(filter=c.search_term, **kw)
 

	
 
        c.users_log = Page(users_log, page=p, items_per_page=10, url=url_generator)
 
        c.users_log = Page(users_log, page=p, items_per_page=10,
 
                           filter=c.search_term)
 

	
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return render('admin/admin_log.html')
kallithea/controllers/admin/auth_settings.py
Show inline comments
 
@@ -37,7 +37,6 @@ from kallithea.lib import auth_modules
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.compat import formatted_json
 
from kallithea.model.db import Setting
 
from kallithea.model.forms import AuthSettingsForm
 
from kallithea.model.meta import Session
 
@@ -87,7 +86,7 @@ class AuthSettingsController(BaseControl
 
        # we want to show , separated list of enabled plugins
 
        c.defaults['auth_plugins'] = ','.join(c.enabled_plugin_names)
 

	
 
        log.debug(formatted_json(defaults))
 
        log.debug('defaults: %s', defaults)
 
        return formencode.htmlfill.render(
 
            render('admin/auth/auth_settings.html'),
 
            defaults=c.defaults,
 
@@ -103,7 +102,7 @@ class AuthSettingsController(BaseControl
 
    def auth_settings(self):
 
        """POST create and store auth settings"""
 
        self.__load_defaults()
 
        log.debug("POST Result: %s", formatted_json(dict(request.POST)))
 
        log.debug("POST Result: %s", dict(request.POST))
 

	
 
        # First, parse only the plugin list (not the plugin settings).
 
        _auth_plugins_validator = AuthSettingsForm([]).fields['auth_plugins']
kallithea/controllers/admin/defaults.py
Show inline comments
 
@@ -31,7 +31,6 @@ import traceback
 
import formencode
 
from formencode import htmlfill
 
from tg import request
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPFound
 

	
 
@@ -69,7 +68,7 @@ class DefaultsController(BaseController)
 

	
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            for k, v in form_result.iteritems():
 
            for k, v in form_result.items():
 
                setting = Setting.create_or_update(k, v)
 
            Session().commit()
 
            h.flash(_('Default settings updated successfully'),
kallithea/controllers/admin/gists.py
Show inline comments
 
@@ -40,7 +40,7 @@ from kallithea.lib import helpers as h
 
from kallithea.lib.auth import LoginRequired
 
from kallithea.lib.base import BaseController, jsonify, render
 
from kallithea.lib.page import Page
 
from kallithea.lib.utils2 import safe_int, safe_unicode, time_to_datetime
 
from kallithea.lib.utils2 import safe_int, safe_str, time_to_datetime
 
from kallithea.lib.vcs.exceptions import NodeNotChangedError, VCSError
 
from kallithea.model.db import Gist
 
from kallithea.model.forms import GistForm
 
@@ -71,6 +71,11 @@ class GistsController(BaseController):
 
        not_default_user = not request.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
 
        url_params = {}
 
        if c.show_public:
 
            url_params['public'] = 1
 
        elif c.show_private:
 
            url_params['private'] = 1
 

	
 
        gists = Gist().query() \
 
            .filter_by(is_expired=False) \
 
@@ -97,7 +102,8 @@ class GistsController(BaseController):
 

	
 
        c.gists = gists
 
        p = safe_int(request.GET.get('page'), 1)
 
        c.gists_pager = Page(c.gists, page=p, items_per_page=10)
 
        c.gists_pager = Page(c.gists, page=p, items_per_page=10,
 
                             **url_params)
 
        return render('admin/gists/index.html')
 

	
 
    @LoginRequired()
 
@@ -176,7 +182,10 @@ class GistsController(BaseController):
 
            log.error(traceback.format_exc())
 
            raise HTTPNotFound()
 
        if format == 'raw':
 
            content = '\n\n'.join([f.content for f in c.files if (f_path is None or safe_unicode(f.path) == f_path)])
 
            content = '\n\n'.join(
 
                safe_str(f.content)
 
                for f in c.files if (f_path is None or f.path == f_path)
 
            )
 
            response.content_type = 'text/plain'
 
            return content
 
        return render('admin/gists/show.html')
kallithea/controllers/admin/my_account.py
Show inline comments
 
@@ -279,8 +279,8 @@ class MyAccountController(BaseController
 
            Session().commit()
 
            SshKeyModel().write_authorized_keys()
 
            h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
 
        except SshKeyModelException as errors:
 
            h.flash(errors.message, category='error')
 
        except SshKeyModelException as e:
 
            h.flash(e.args[0], category='error')
 
        raise HTTPFound(location=url('my_account_ssh_keys'))
 

	
 
    @IfSshEnabled
 
@@ -291,6 +291,6 @@ class MyAccountController(BaseController
 
            Session().commit()
 
            SshKeyModel().write_authorized_keys()
 
            h.flash(_("SSH key successfully deleted"), category='success')
 
        except SshKeyModelException as errors:
 
            h.flash(errors.message, category='error')
 
        except SshKeyModelException as e:
 
            h.flash(e.args[0], category='error')
 
        raise HTTPFound(location=url('my_account_ssh_keys'))
kallithea/controllers/admin/repo_groups.py
Show inline comments
 
@@ -25,7 +25,6 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import itertools
 
import logging
 
import traceback
 

	
 
@@ -37,7 +36,6 @@ from tg.i18n import ugettext as _
 
from tg.i18n import ungettext
 
from webob.exc import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPNotFound
 

	
 
import kallithea
 
from kallithea.config.routing import url
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasPermissionAny, HasRepoGroupPermissionLevel, HasRepoGroupPermissionLevelDecorator, LoginRequired
 
@@ -93,10 +91,8 @@ class RepoGroupsController(BaseControlle
 
        return data
 

	
 
    def _revoke_perms_on_yourself(self, form_result):
 
        _up = filter(lambda u: request.authuser.username == u[0],
 
                     form_result['perms_updates'])
 
        _new = filter(lambda u: request.authuser.username == u[0],
 
                      form_result['perms_new'])
 
        _up = [u for u in form_result['perms_updates'] if request.authuser.username == u[0]]
 
        _new = [u for u in form_result['perms_new'] if request.authuser.username == u[0]]
 
        if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
 
            return True
 
        return False
 
@@ -105,24 +101,20 @@ class RepoGroupsController(BaseControlle
 
        _list = RepoGroup.query(sorted=True).all()
 
        group_iter = RepoGroupList(_list, perm_level='admin')
 
        repo_groups_data = []
 
        total_records = len(group_iter)
 
        _tmpl_lookup = app_globals.mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        repo_group_name = lambda repo_group_name, children_groups: (
 
            template.get_def("repo_group_name")
 
            .render(repo_group_name, children_groups, _=_, h=h, c=c)
 
        )
 
        repo_group_actions = lambda repo_group_id, repo_group_name, gr_count: (
 
            template.get_def("repo_group_actions")
 
            .render(repo_group_id, repo_group_name, gr_count, _=_, h=h, c=c,
 
        def repo_group_name(repo_group_name, children_groups):
 
            return template.get_def("repo_group_name") \
 
                .render_unicode(repo_group_name, children_groups, _=_, h=h, c=c)
 

	
 
        def repo_group_actions(repo_group_id, repo_group_name, gr_count):
 
            return template.get_def("repo_group_actions") \
 
                .render_unicode(repo_group_id, repo_group_name, gr_count, _=_, h=h, c=c,
 
                    ungettext=ungettext)
 
        )
 

	
 
        for repo_gr in group_iter:
 
            children_groups = map(h.safe_unicode,
 
                itertools.chain((g.name for g in repo_gr.parents),
 
                                (x.name for x in [repo_gr])))
 
            children_groups = [g.name for g in repo_gr.parents] + [repo_gr.name]
 
            repo_count = repo_gr.repositories.count()
 
            repo_groups_data.append({
 
                "raw_name": repo_gr.group_name,
kallithea/controllers/admin/repos.py
Show inline comments
 
@@ -28,6 +28,7 @@ Original author and date, and relevant c
 
import logging
 
import traceback
 

	
 
import celery.result
 
import formencode
 
from formencode import htmlfill
 
from tg import request
 
@@ -35,6 +36,7 @@ from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPNotFound
 

	
 
import kallithea
 
from kallithea.config.routing import url
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasPermissionAny, HasRepoPermissionLevelDecorator, LoginRequired, NotAnonymous
 
@@ -43,7 +45,7 @@ from kallithea.lib.exceptions import Att
 
from kallithea.lib.utils import action_logger
 
from kallithea.lib.utils2 import safe_int
 
from kallithea.lib.vcs import RepositoryError
 
from kallithea.model.db import RepoGroup, Repository, RepositoryField, Setting, User, UserFollowing
 
from kallithea.model.db import RepoGroup, Repository, RepositoryField, Setting, UserFollowing
 
from kallithea.model.forms import RepoFieldForm, RepoForm, RepoPermsForm
 
from kallithea.model.meta import Session
 
from kallithea.model.repo import RepoModel
 
@@ -110,17 +112,11 @@ class ReposController(BaseRepoController
 
    @NotAnonymous()
 
    def create(self):
 
        self.__load_defaults()
 
        form_result = {}
 
        try:
 
            # CanWriteGroup validators checks permissions of this POST
 
            form_result = RepoForm(repo_groups=c.repo_groups,
 
                                   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, request.authuser.user_id)
 
            task_id = task.task_id
 
        except formencode.Invalid as errors:
 
            log.info(errors)
 
            return htmlfill.render(
 
@@ -131,6 +127,11 @@ class ReposController(BaseRepoController
 
                force_defaults=False,
 
                encoding="UTF-8")
 

	
 
        try:
 
            # create is done sometimes async on celery, db transaction
 
            # management is handled there.
 
            task = RepoModel().create(form_result, request.authuser.user_id)
 
            task_id = task.task_id
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            msg = (_('Error creating repository %s')
 
@@ -181,12 +182,10 @@ class ReposController(BaseRepoController
 
        task_id = request.GET.get('task_id')
 

	
 
        if task_id and task_id not in ['None']:
 
            from kallithea import CELERY_ON
 
            from kallithea.lib import celerypylons
 
            if CELERY_ON:
 
                task = celerypylons.result.AsyncResult(task_id)
 
                if task.failed():
 
                    raise HTTPInternalServerError(task.traceback)
 
            if kallithea.CELERY_APP:
 
                task_result = celery.result.AsyncResult(task_id, app=kallithea.CELERY_APP)
 
                if task_result.failed():
 
                    raise HTTPInternalServerError(task_result.traceback)
 

	
 
        repo = Repository.get_by_repo_name(repo_name)
 
        if repo and repo.repo_state == Repository.STATE_CREATED:
 
@@ -406,7 +405,7 @@ class ReposController(BaseRepoController
 
    @HasRepoPermissionLevelDecorator('admin')
 
    def edit_advanced(self, repo_name):
 
        c.repo_info = self._load_repo()
 
        c.default_user_id = User.get_default_user().user_id
 
        c.default_user_id = kallithea.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()
 
@@ -443,7 +442,7 @@ class ReposController(BaseRepoController
 

	
 
        try:
 
            repo_id = Repository.get_by_repo_name(repo_name).repo_id
 
            user_id = User.get_default_user().user_id
 
            user_id = kallithea.DEFAULT_USER_ID
 
            self.scm_model.toggle_following_repo(repo_id, user_id)
 
            h.flash(_('Updated repository visibility in public journal'),
 
                    category='success')
 
@@ -471,7 +470,7 @@ class ReposController(BaseRepoController
 
                    category='success')
 
        except RepositoryError as e:
 
            log.error(traceback.format_exc())
 
            h.flash(str(e), category='error')
 
            h.flash(e, category='error')
 
        except Exception as e:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during this operation'),
 
@@ -480,24 +479,6 @@ class ReposController(BaseRepoController
 
        raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name))
 

	
 
    @HasRepoPermissionLevelDecorator('admin')
 
    def edit_caches(self, repo_name):
 
        c.repo_info = self._load_repo()
 
        c.active = 'caches'
 
        if request.POST:
 
            try:
 
                ScmModel().mark_for_invalidation(repo_name)
 
                Session().commit()
 
                h.flash(_('Cache invalidation successful'),
 
                        category='success')
 
            except Exception as e:
 
                log.error(traceback.format_exc())
 
                h.flash(_('An error occurred during cache invalidation'),
 
                        category='error')
 

	
 
            raise HTTPFound(location=url('edit_repo_caches', repo_name=c.repo_name))
 
        return render('admin/repos/repo_edit.html')
 

	
 
    @HasRepoPermissionLevelDecorator('admin')
 
    def edit_remote(self, repo_name):
 
        c.repo_info = self._load_repo()
 
        c.active = 'remote'
kallithea/controllers/admin/settings.py
Show inline comments
 
@@ -42,7 +42,7 @@ from kallithea.lib.base import BaseContr
 
from kallithea.lib.celerylib import tasks
 
from kallithea.lib.exceptions import HgsubversionImportError
 
from kallithea.lib.utils import repo2db_mapper, set_app_settings
 
from kallithea.lib.utils2 import safe_unicode
 
from kallithea.lib.utils2 import safe_str
 
from kallithea.lib.vcs import VCSError
 
from kallithea.model.db import Repository, Setting, Ui
 
from kallithea.model.forms import ApplicationSettingsForm, ApplicationUiSettingsForm, ApplicationVisualisationForm
 
@@ -120,6 +120,7 @@ class SettingsController(BaseController)
 
                if sett.ui_active:
 
                    try:
 
                        import hgsubversion  # pragma: no cover
 
                        assert hgsubversion
 
                    except ImportError:
 
                        raise HgsubversionImportError
 

	
 
@@ -168,10 +169,10 @@ class SettingsController(BaseController)
 
                                            user=request.authuser.username,
 
                                            overwrite_git_hooks=overwrite_git_hooks)
 
            added_msg = h.HTML(', ').join(
 
                h.link_to(safe_unicode(repo_name), h.url('summary_home', repo_name=repo_name)) for repo_name in added
 
                h.link_to(safe_str(repo_name), h.url('summary_home', repo_name=repo_name)) for repo_name in added
 
            ) or '-'
 
            removed_msg = h.HTML(', ').join(
 
                safe_unicode(repo_name) for repo_name in removed
 
                safe_str(repo_name) for repo_name in removed
 
            ) or '-'
 
            h.flash(h.HTML(_('Repositories successfully rescanned. Added: %s. Removed: %s.')) %
 
                    (added_msg, removed_msg), category='success')
 
@@ -423,7 +424,7 @@ class SettingsController(BaseController)
 
        import kallithea
 
        c.ini = kallithea.CONFIG
 
        server_info = Setting.get_server_info()
 
        for key, val in server_info.iteritems():
 
        for key, val in server_info.items():
 
            setattr(c, key, val)
 

	
 
        return htmlfill.render(
kallithea/controllers/admin/user_groups.py
Show inline comments
 
@@ -32,19 +32,18 @@ import formencode
 
from formencode import htmlfill
 
from sqlalchemy.orm import joinedload
 
from sqlalchemy.sql.expression import func
 
from tg import app_globals, config, request
 
from tg import app_globals, request
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPFound, HTTPInternalServerError
 

	
 
import kallithea
 
from kallithea.config.routing import url
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasPermissionAnyDecorator, HasUserGroupPermissionLevelDecorator, LoginRequired
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.exceptions import RepoGroupAssignmentError, UserGroupsAssignedException
 
from kallithea.lib.utils import action_logger
 
from kallithea.lib.utils2 import safe_int, safe_unicode
 
from kallithea.lib.utils2 import safe_int, safe_str
 
from kallithea.model.db import User, UserGroup, UserGroupRepoGroupToPerm, UserGroupRepoToPerm, UserGroupToPerm
 
from kallithea.model.forms import CustomDefaultPermissionsForm, UserGroupForm, UserGroupPermsForm
 
from kallithea.model.meta import Session
 
@@ -61,7 +60,6 @@ class UserGroupsController(BaseControlle
 
    @LoginRequired(allow_default_user=True)
 
    def _before(self, *args, **kwargs):
 
        super(UserGroupsController, self)._before(*args, **kwargs)
 
        c.available_permissions = config['available_permissions']
 

	
 
    def __load_data(self, user_group_id):
 
        c.group_members_obj = sorted((x.user for x in c.user_group.members),
 
@@ -88,20 +86,18 @@ class UserGroupsController(BaseControlle
 
                        .all()
 
        group_iter = UserGroupList(_list, perm_level='admin')
 
        user_groups_data = []
 
        total_records = len(group_iter)
 
        _tmpl_lookup = app_globals.mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        user_group_name = lambda user_group_id, user_group_name: (
 
            template.get_def("user_group_name")
 
            .render(user_group_id, user_group_name, _=_, h=h, c=c)
 
        )
 
        user_group_actions = lambda user_group_id, user_group_name: (
 
            template.get_def("user_group_actions")
 
            .render(user_group_id, user_group_name, _=_, h=h, c=c)
 
        )
 
        def user_group_name(user_group_id, user_group_name):
 
            return template.get_def("user_group_name") \
 
                .render_unicode(user_group_id, user_group_name, _=_, h=h, c=c)
 

	
 
        def user_group_actions(user_group_id, user_group_name):
 
            return template.get_def("user_group_actions") \
 
                .render_unicode(user_group_id, user_group_name, _=_, h=h, c=c)
 

	
 
        for user_gr in group_iter:
 

	
 
            user_groups_data.append({
 
                "raw_name": user_gr.users_group_name,
 
                "group_name": user_group_name(user_gr.users_group_id,
 
@@ -163,7 +159,7 @@ class UserGroupsController(BaseControlle
 
        c.active = 'settings'
 
        self.__load_data(id)
 

	
 
        available_members = [safe_unicode(x[0]) for x in c.available_members]
 
        available_members = [safe_str(x[0]) for x in c.available_members]
 

	
 
        users_group_form = UserGroupForm(edit=True,
 
                                         old_data=c.user_group.get_dict(),
kallithea/controllers/admin/users.py
Show inline comments
 
@@ -31,7 +31,7 @@ import traceback
 
import formencode
 
from formencode import htmlfill
 
from sqlalchemy.sql.expression import func
 
from tg import app_globals, config, request
 
from tg import app_globals, request
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPFound, HTTPNotFound
 
@@ -63,7 +63,6 @@ class UsersController(BaseController):
 
    @HasPermissionAnyDecorator('hg.admin')
 
    def _before(self, *args, **kwargs):
 
        super(UsersController, self)._before(*args, **kwargs)
 
        c.available_permissions = config['available_permissions']
 

	
 
    def index(self, format='html'):
 
        c.users_list = User.query().order_by(User.username) \
 
@@ -72,19 +71,18 @@ class UsersController(BaseController):
 
                        .all()
 

	
 
        users_data = []
 
        total_records = len(c.users_list)
 
        _tmpl_lookup = app_globals.mako_lookup
 
        template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 

	
 
        grav_tmpl = '<div class="gravatar">%s</div>'
 

	
 
        username = lambda user_id, username: (
 
                template.get_def("user_name")
 
                .render(user_id, username, _=_, h=h, c=c))
 
        def username(user_id, username):
 
            return template.get_def("user_name") \
 
                .render_unicode(user_id, username, _=_, h=h, c=c)
 

	
 
        user_actions = lambda user_id, username: (
 
                template.get_def("user_actions")
 
                .render(user_id, username, _=_, h=h, c=c))
 
        def user_actions(user_id, username):
 
            return template.get_def("user_actions") \
 
                .render_unicode(user_id, username, _=_, h=h, c=c)
 

	
 
        for user in c.users_list:
 
            users_data.append({
 
@@ -390,7 +388,7 @@ class UsersController(BaseController):
 
            .filter(UserIpMap.user == c.user).all()
 

	
 
        c.default_user_ip_map = UserIpMap.query() \
 
            .filter(UserIpMap.user == User.get_default_user()).all()
 
            .filter(UserIpMap.user_id == kallithea.DEFAULT_USER_ID).all()
 

	
 
        defaults = c.user.get_dict()
 
        return htmlfill.render(
 
@@ -454,8 +452,8 @@ class UsersController(BaseController):
 
            Session().commit()
 
            SshKeyModel().write_authorized_keys()
 
            h.flash(_("SSH key %s successfully added") % new_ssh_key.fingerprint, category='success')
 
        except SshKeyModelException as errors:
 
            h.flash(errors.message, category='error')
 
        except SshKeyModelException as e:
 
            h.flash(e.args[0], category='error')
 
        raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
 

	
 
    @IfSshEnabled
 
@@ -468,6 +466,6 @@ class UsersController(BaseController):
 
            Session().commit()
 
            SshKeyModel().write_authorized_keys()
 
            h.flash(_("SSH key successfully deleted"), category='success')
 
        except SshKeyModelException as errors:
 
            h.flash(errors.message, category='error')
 
        except SshKeyModelException as e:
 
            h.flash(e.args[0], category='error')
 
        raise HTTPFound(location=url('edit_user_ssh_keys', id=c.user.user_id))
kallithea/controllers/api/__init__.py
Show inline comments
 
@@ -35,11 +35,11 @@ import types
 
from tg import Response, TGController, request, response
 
from webob.exc import HTTPError, HTTPException
 

	
 
from kallithea.lib import ext_json
 
from kallithea.lib.auth import AuthUser
 
from kallithea.lib.base import _get_access_path
 
from kallithea.lib.base import _get_ip_addr as _get_ip
 
from kallithea.lib.compat import json
 
from kallithea.lib.utils2 import safe_str, safe_unicode
 
from kallithea.lib.base import get_path_info
 
from kallithea.lib.utils2 import ascii_bytes
 
from kallithea.model.db import User
 

	
 

	
 
@@ -53,7 +53,7 @@ class JSONRPCError(BaseException):
 
        super(JSONRPCError, self).__init__()
 

	
 
    def __str__(self):
 
        return safe_str(self.message)
 
        return self.message
 

	
 

	
 
class JSONRPCErrorResponse(Response, HTTPException):
 
@@ -121,7 +121,7 @@ class JSONRPCController(TGController):
 
        raw_body = environ['wsgi.input'].read(length)
 

	
 
        try:
 
            json_body = json.loads(raw_body)
 
            json_body = ext_json.loads(raw_body)
 
        except ValueError as e:
 
            # catch JSON errors Here
 
            raise JSONRPCErrorResponse(retid=self._req_id,
 
@@ -166,13 +166,13 @@ class JSONRPCController(TGController):
 

	
 
        # now that we have a method, add self._req_params to
 
        # self.kargs and dispatch control to WGIController
 
        argspec = inspect.getargspec(self._func)
 
        arglist = argspec[0][1:]
 
        defaults = map(type, argspec[3] or [])
 
        default_empty = types.NotImplementedType
 
        argspec = inspect.getfullargspec(self._func)
 
        arglist = argspec.args[1:]
 
        argtypes = [type(arg) for arg in argspec.defaults or []]
 
        default_empty = type(NotImplemented)
 

	
 
        # kw arguments required by this method
 
        func_kwargs = dict(itertools.izip_longest(reversed(arglist), reversed(defaults),
 
        func_kwargs = dict(itertools.zip_longest(reversed(arglist), reversed(argtypes),
 
                                                  fillvalue=default_empty))
 

	
 
        # This attribute will need to be first param of a method that uses
 
@@ -180,7 +180,7 @@ class JSONRPCController(TGController):
 
        USER_SESSION_ATTR = 'apiuser'
 

	
 
        # get our arglist and check if we provided them as args
 
        for arg, default in func_kwargs.iteritems():
 
        for arg, default in func_kwargs.items():
 
            if arg == USER_SESSION_ATTR:
 
                # USER_SESSION_ATTR is something translated from API key and
 
                # this is checked before so we don't need validate it
 
@@ -209,7 +209,7 @@ class JSONRPCController(TGController):
 

	
 
        log.info('IP: %s Request to %s time: %.3fs' % (
 
            self._get_ip_addr(environ),
 
            safe_unicode(_get_access_path(environ)), time.time() - start)
 
            get_path_info(environ), time.time() - start)
 
        )
 

	
 
        state.set_action(self._rpc_call, [])
 
@@ -226,28 +226,28 @@ class JSONRPCController(TGController):
 
            if isinstance(raw_response, HTTPError):
 
                self._error = str(raw_response)
 
        except JSONRPCError as e:
 
            self._error = safe_str(e)
 
            self._error = str(e)
 
        except Exception as e:
 
            log.error('Encountered unhandled exception: %s',
 
                      traceback.format_exc(),)
 
            json_exc = JSONRPCError('Internal server error')
 
            self._error = safe_str(json_exc)
 
            self._error = str(json_exc)
 

	
 
        if self._error is not None:
 
            raw_response = None
 

	
 
        response = dict(id=self._req_id, result=raw_response, error=self._error)
 
        try:
 
            return json.dumps(response)
 
            return ascii_bytes(ext_json.dumps(response))
 
        except TypeError as e:
 
            log.error('API FAILED. Error encoding response: %s', e)
 
            return json.dumps(
 
            log.error('API FAILED. Error encoding response for %s %s: %s\n%s', action, rpc_args, e, traceback.format_exc())
 
            return ascii_bytes(ext_json.dumps(
 
                dict(
 
                    id=self._req_id,
 
                    result=None,
 
                    error="Error encoding response"
 
                    error="Error encoding response",
 
                )
 
            )
 
            ))
 

	
 
    def _find_method(self):
 
        """
kallithea/controllers/api/api.py
Show inline comments
 
@@ -32,8 +32,8 @@ from datetime import datetime
 
from tg import request
 

	
 
from kallithea.controllers.api import JSONRPCController, JSONRPCError
 
from kallithea.lib.auth import (
 
    AuthUser, HasPermissionAny, HasPermissionAnyDecorator, HasRepoGroupPermissionLevel, HasRepoPermissionLevel, HasUserGroupPermissionLevel)
 
from kallithea.lib.auth import (AuthUser, HasPermissionAny, HasPermissionAnyDecorator, HasRepoGroupPermissionLevel, HasRepoPermissionLevel,
 
                                HasUserGroupPermissionLevel)
 
from kallithea.lib.exceptions import DefaultUserException, UserGroupsAssignedException
 
from kallithea.lib.utils import action_logger, repo2db_mapper
 
from kallithea.lib.utils2 import OAttr, Optional
 
@@ -433,7 +433,7 @@ class ApiController(JSONRPCController):
 

	
 
    @HasPermissionAnyDecorator('hg.admin')
 
    def create_user(self, username, email, password=Optional(''),
 
                    firstname=Optional(u''), lastname=Optional(u''),
 
                    firstname=Optional(''), lastname=Optional(''),
 
                    active=Optional(True), admin=Optional(False),
 
                    extern_type=Optional(User.DEFAULT_AUTH_TYPE),
 
                    extern_name=Optional('')):
 
@@ -686,7 +686,7 @@ class ApiController(JSONRPCController):
 
        ]
 

	
 
    @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
 
    def create_user_group(self, group_name, description=Optional(u''),
 
    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
 
@@ -1160,7 +1160,7 @@ class ApiController(JSONRPCController):
 
            return _map[ret_type]
 
        except KeyError:
 
            raise JSONRPCError('ret_type must be one of %s'
 
                               % (','.join(_map.keys())))
 
                               % (','.join(sorted(_map))))
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
@@ -2339,7 +2339,7 @@ class ApiController(JSONRPCController):
 
                                                 branch_name,
 
                                                 reverse, max_revisions)]
 
        except EmptyRepositoryError as e:
 
            raise JSONRPCError(e.message)
 
            raise JSONRPCError('Repository is empty')
 

	
 
    # permission check inside
 
    def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
 
@@ -2373,7 +2373,7 @@ class ApiController(JSONRPCController):
 
        return pull_request.get_api_data()
 

	
 
    # permission check inside
 
    def comment_pullrequest(self, pull_request_id, comment_msg=u'', status=None, close_pr=False):
 
    def comment_pullrequest(self, pull_request_id, comment_msg='', status=None, close_pr=False):
 
        """
 
        Add comment, close and change status of pull request.
 
        """
 
@@ -2400,7 +2400,7 @@ class ApiController(JSONRPCController):
 
            pull_request=pull_request.pull_request_id,
 
            f_path=None,
 
            line_no=None,
 
            status_change=(ChangesetStatus.get_status_lbl(status)),
 
            status_change=ChangesetStatus.get_status_lbl(status),
 
            closing_pr=close_pr
 
        )
 
        action_logger(apiuser,
kallithea/controllers/changelog.py
Show inline comments
 
@@ -38,8 +38,8 @@ from kallithea.config.routing import url
 
from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.graphmod import graph_data
 
from kallithea.lib.page import RepoPage
 
from kallithea.lib.utils2 import safe_int, safe_str
 
from kallithea.lib.page import Page
 
from kallithea.lib.utils2 import safe_int
 
from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, ChangesetError, EmptyRepositoryError, NodeDoesNotExistError, RepositoryError
 

	
 

	
 
@@ -67,7 +67,7 @@ class ChangelogController(BaseRepoContro
 
            h.flash(_('There are no changesets yet'), category='error')
 
        except RepositoryError as e:
 
            log.error(traceback.format_exc())
 
            h.flash(safe_str(e), category='error')
 
            h.flash(e, category='error')
 
        raise HTTPBadRequest()
 

	
 
    @LoginRequired(allow_default_user=True)
 
@@ -111,35 +111,34 @@ class ChangelogController(BaseRepoContro
 
                        cs = self.__get_cs(revision, repo_name)
 
                        collection = cs.get_file_history(f_path)
 
                    except RepositoryError as e:
 
                        h.flash(safe_str(e), category='warning')
 
                        h.flash(e, category='warning')
 
                        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,
 
                                                        branch_name=branch_name)
 
                                                        branch_name=branch_name, reverse=True)
 
            c.total_cs = len(collection)
 

	
 
            c.cs_pagination = RepoPage(collection, page=p, item_count=c.total_cs,
 
                                    items_per_page=c.size, branch=branch_name,)
 
            c.cs_pagination = Page(collection, page=p, item_count=c.total_cs, items_per_page=c.size,
 
                                   branch=branch_name)
 

	
 
            page_revisions = [x.raw_id for x in c.cs_pagination]
 
            c.cs_comments = c.db_repo.get_comments(page_revisions)
 
            c.cs_statuses = c.db_repo.statuses(page_revisions)
 
        except EmptyRepositoryError as e:
 
            h.flash(safe_str(e), category='warning')
 
            h.flash(e, category='warning')
 
            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')
 
            h.flash(e, category='error')
 
            raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name))
 

	
 
        c.branch_name = branch_name
 
        c.branch_filters = [('', _('None'))] + \
 
            [(k, k) for k in c.db_repo_scm_instance.branches.keys()]
 
            [(k, k) for k in c.db_repo_scm_instance.branches]
 
        if c.db_repo_scm_instance.closed_branches:
 
            prefix = _('(closed)') + ' '
 
            c.branch_filters += [('-', '-')] + \
 
                [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches.keys()]
 
                [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches]
 
        revs = []
 
        if not f_path:
 
            revs = [x.revision for x in c.cs_pagination]
kallithea/controllers/changeset.py
Show inline comments
 
@@ -25,6 +25,7 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import binascii
 
import logging
 
import traceback
 
from collections import OrderedDict, defaultdict
 
@@ -32,7 +33,7 @@ from collections import OrderedDict, def
 
from tg import request, response
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPFound, HTTPNotFound
 
from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPNotFound
 

	
 
import kallithea.lib.helpers as h
 
from kallithea.lib import diffs
 
@@ -40,7 +41,7 @@ from kallithea.lib.auth import HasRepoPe
 
from kallithea.lib.base import BaseRepoController, jsonify, render
 
from kallithea.lib.graphmod import graph_data
 
from kallithea.lib.utils import action_logger
 
from kallithea.lib.utils2 import safe_unicode
 
from kallithea.lib.utils2 import ascii_str, safe_str
 
from kallithea.lib.vcs.backends.base import EmptyChangeset
 
from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError
 
from kallithea.model.changeset_status import ChangesetStatusModel
 
@@ -65,7 +66,7 @@ def anchor_url(revision, path, GET):
 

	
 
def get_ignore_ws(fid, GET):
 
    ig_ws_global = GET.get('ignorews')
 
    ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
 
    ig_ws = [k for k in GET.getall(fid) if k.startswith('WS')]
 
    if ig_ws:
 
        try:
 
            return int(ig_ws[0].split(':')[-1])
 
@@ -108,9 +109,9 @@ def _ignorews_url(GET, fileid=None):
 
def get_line_ctx(fid, GET):
 
    ln_ctx_global = GET.get('context')
 
    if fid:
 
        ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
 
        ln_ctx = [k for k in GET.getall(fid) if k.startswith('C')]
 
    else:
 
        _ln_ctx = filter(lambda k: k.startswith('C'), GET)
 
        _ln_ctx = [k for k in GET if k.startswith('C')]
 
        ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
 
        if ln_ctx:
 
            ln_ctx = [ln_ctx]
 
@@ -214,7 +215,6 @@ def create_cs_pr_comment(repo_name, revi
 
            return {
 
               'location': h.url('my_pullrequests'), # or repo pr list?
 
            }
 
            raise HTTPFound(location=h.url('my_pullrequests')) # or repo pr list?
 
        raise HTTPForbidden()
 

	
 
    text = request.POST.get('text', '').strip()
 
@@ -256,7 +256,7 @@ def create_cs_pr_comment(repo_name, revi
 
    Session().commit()
 

	
 
    data = {
 
       'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
 
       'target_id': h.safeid(request.POST.get('f_path')),
 
    }
 
    if comment is not None:
 
        c.comment = comment
 
@@ -395,6 +395,8 @@ class ChangesetController(BaseRepoContro
 
            c.changeset = c.cs_ranges[0]
 
            c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
 
                                     for x in c.changeset.parents])
 
            c.changeset_graft_source_hash = ascii_str(c.changeset.extra.get(b'source', b''))
 
            c.changeset_transplant_source_hash = ascii_str(binascii.hexlify(c.changeset.extra.get(b'transplant_source', b'')))
 
        if method == 'download':
 
            response.content_type = 'text/plain'
 
            response.content_disposition = 'attachment; filename=%s.diff' \
 
@@ -402,7 +404,7 @@ class ChangesetController(BaseRepoContro
 
            return raw_diff
 
        elif method == 'patch':
 
            response.content_type = 'text/plain'
 
            c.diff = safe_unicode(raw_diff)
 
            c.diff = safe_str(raw_diff)
 
            return render('changeset/patch_changeset.html')
 
        elif method == 'raw':
 
            response.content_type = 'text/plain'
kallithea/controllers/compare.py
Show inline comments
 
@@ -30,6 +30,7 @@ Original author and date, and relevant c
 
import logging
 
import re
 

	
 
import mercurial.unionrepo
 
from tg import request
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
@@ -42,8 +43,7 @@ from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.graphmod import graph_data
 
from kallithea.lib.utils2 import safe_int, safe_str
 
from kallithea.lib.vcs.utils.hgcompat import unionrepo
 
from kallithea.lib.utils2 import ascii_bytes, ascii_str, safe_bytes, safe_int
 
from kallithea.model.db import Repository
 

	
 

	
 
@@ -97,14 +97,9 @@ class CompareController(BaseRepoControll
 
        elif alias == 'hg':
 
            # case two independent repos
 
            if org_repo != other_repo:
 
                try:
 
                    hgrepo = unionrepo.makeunionrepository(other_repo.baseui,
 
                                                           other_repo.path,
 
                                                           org_repo.path)
 
                except AttributeError: # makeunionrepository was introduced in Mercurial 4.8 23f2299e9e53
 
                    hgrepo = unionrepo.unionrepository(other_repo.baseui,
 
                                                       other_repo.path,
 
                                                       org_repo.path)
 
                hgrepo = mercurial.unionrepo.makeunionrepository(other_repo.baseui,
 
                                                       safe_bytes(other_repo.path),
 
                                                       safe_bytes(org_repo.path))
 
                # all ancestors of other_rev will be in other_repo and
 
                # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot
 

	
 
@@ -112,21 +107,27 @@ class CompareController(BaseRepoControll
 
            else:
 
                hgrepo = other_repo._repo
 

	
 
            ancestors = [hgrepo[ancestor].hex() for ancestor in
 
                         hgrepo.revs("id(%s) & ::id(%s)", other_rev, org_rev)]
 
            ancestors = [ascii_str(hgrepo[ancestor].hex()) for ancestor in
 
                         hgrepo.revs(b"id(%s) & ::id(%s)", ascii_bytes(other_rev), ascii_bytes(org_rev))]
 
            if ancestors:
 
                log.debug("shortcut found: %s is already an ancestor of %s", other_rev, org_rev)
 
            else:
 
                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!
 
                ancestors = [ascii_str(hgrepo[ancestor].hex()) for ancestor in
 
                             hgrepo.revs(b"heads(::id(%s) & ::id(%s))", ascii_bytes(org_rev), ascii_bytes(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]
 
            other_changesets = [
 
                other_repo.get_changeset(rev)
 
                for rev in hgrepo.revs(
 
                    b"ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
 
                    ascii_bytes(other_rev), ascii_bytes(org_rev), ascii_bytes(org_rev))
 
            ]
 
            org_changesets = [
 
                org_repo.get_changeset(ascii_str(hgrepo[rev].hex()))
 
                for rev in hgrepo.revs(
 
                    b"ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
 
                    ascii_bytes(org_rev), ascii_bytes(other_rev), ascii_bytes(other_rev))
 
            ]
 

	
 
        elif alias == 'git':
 
            if org_repo != other_repo:
 
@@ -134,15 +135,15 @@ class CompareController(BaseRepoControll
 
                from dulwich.client import SubprocessGitClient
 

	
 
                gitrepo = Repo(org_repo.path)
 
                SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo)
 
                SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo)
 

	
 
                gitrepo_remote = Repo(other_repo.path)
 
                SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote)
 
                SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote)
 

	
 
                revs = [
 
                    x.commit.id
 
                    for x in gitrepo_remote.get_walker(include=[other_rev],
 
                                                       exclude=[org_rev])
 
                    ascii_str(x.commit.id)
 
                    for x in gitrepo_remote.get_walker(include=[ascii_bytes(other_rev)],
 
                                                       exclude=[ascii_bytes(org_rev)])
 
                ]
 
                other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
 
                if other_changesets:
 
@@ -155,13 +156,13 @@ class CompareController(BaseRepoControll
 
                gitrepo_remote.close()
 

	
 
            else:
 
                so, se = org_repo.run_git_command(
 
                so = org_repo.run_git_command(
 
                    ['log', '--reverse', '--pretty=format:%H',
 
                     '-s', '%s..%s' % (org_rev, other_rev)]
 
                )
 
                other_changesets = [org_repo.get_changeset(cs)
 
                              for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
 
                so, se = org_repo.run_git_command(
 
                so = org_repo.run_git_command(
 
                    ['merge-base', org_rev, other_rev]
 
                )
 
                ancestors = [re.findall(r'[0-9a-fA-F]{40}', so)[0]]
 
@@ -277,7 +278,7 @@ class CompareController(BaseRepoControll
 
                                      ignore_whitespace=ignore_whitespace,
 
                                      context=line_context)
 

	
 
        diff_processor = diffs.DiffProcessor(raw_diff or '', diff_limit=diff_limit)
 
        diff_processor = diffs.DiffProcessor(raw_diff, diff_limit=diff_limit)
 
        c.limited_diff = diff_processor.limited_diff
 
        c.file_diff_data = []
 
        c.lines_added = 0
kallithea/controllers/error.py
Show inline comments
 
@@ -25,7 +25,7 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import cgi
 
import html
 
import logging
 

	
 
from tg import config, expose, request
 
@@ -64,8 +64,7 @@ class ErrorController(BaseController):
 
            'protocol': e.get('wsgi.url_scheme'),
 
            'host': e.get('HTTP_HOST'), }
 
        if resp:
 
            c.error_message = cgi.escape(request.GET.get('code',
 
                                                         str(resp.status)))
 
            c.error_message = html.escape(request.GET.get('code', str(resp.status)))
 
            c.error_explanation = self.get_error_explanation(resp.status_int)
 
        else:
 
            c.error_message = _('No response')
kallithea/controllers/feed.py
Show inline comments
 
@@ -32,23 +32,19 @@ from beaker.cache import cache_region
 
from tg import response
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 

	
 
from kallithea import CONFIG
 
from kallithea.lib import feeds
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 
from kallithea.lib.base import BaseRepoController
 
from kallithea.lib.diffs import DiffProcessor
 
from kallithea.lib.utils2 import safe_int, safe_unicode, str2bool
 
from kallithea.lib.utils2 import safe_int, safe_str, str2bool
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
language = 'en-us'
 
ttl = "5"
 

	
 

	
 
class FeedController(BaseRepoController):
 

	
 
    @LoginRequired(allow_default_user=True)
 
@@ -98,64 +94,41 @@ class FeedController(BaseRepoController)
 
        desc_msg.extend(changes)
 
        if str2bool(CONFIG.get('rss_include_diff', False)):
 
            desc_msg.append('\n\n')
 
            desc_msg.append(raw_diff)
 
            desc_msg.append(safe_str(raw_diff))
 
        desc_msg.append('</pre>')
 
        return map(safe_unicode, desc_msg)
 
        return desc_msg
 

	
 
    def atom(self, repo_name):
 
        """Produce an atom-1.0 feed via feedgenerator module"""
 
    def _feed(self, repo_name, feeder):
 
        """Produce a simple feed"""
 

	
 
        @cache_region('long_term', '_get_feed_from_cache')
 
        @cache_region('long_term_file', '_get_feed_from_cache')
 
        def _get_feed_from_cache(*_cache_keys):  # parameters are not really used - only as caching key
 
            feed = Atom1Feed(
 
            header = dict(
 
                title=_('%s %s feed') % (c.site_name, repo_name),
 
                link=h.canonical_url('summary_home', repo_name=repo_name),
 
                description=_('Changes on %s repository') % repo_name,
 
                language=language,
 
                ttl=ttl
 
            )
 

	
 
            rss_items_per_page = safe_int(CONFIG.get('rss_items_per_page', 20))
 
            entries=[]
 
            for cs in reversed(list(c.db_repo_scm_instance[-rss_items_per_page:])):
 
                feed.add_item(title=self._get_title(cs),
 
                              link=h.canonical_url('changeset_home', repo_name=repo_name,
 
                                       revision=cs.raw_id),
 
                              author_name=cs.author,
 
                entries.append(dict(
 
                    title=self._get_title(cs),
 
                    link=h.canonical_url('changeset_home', repo_name=repo_name, revision=cs.raw_id),
 
                    author_email=cs.author_email,
 
                    author_name=cs.author_name,
 
                              description=''.join(self.__get_desc(cs)),
 
                              pubdate=cs.date,
 
                              )
 
                ))
 
            return feeder.render(header, entries)
 

	
 
            response.content_type = feed.mime_type
 
            return feed.writeString('utf-8')
 
        response.content_type = feeder.content_type
 
        return _get_feed_from_cache(repo_name, feeder.__name__)
 

	
 
        kind = 'ATOM'
 
        return _get_feed_from_cache(repo_name, kind, c.db_repo.changeset_cache.get('raw_id'))
 
    def atom(self, repo_name):
 
        """Produce a simple atom-1.0 feed"""
 
        return self._feed(repo_name, feeds.AtomFeed)
 

	
 
    def rss(self, repo_name):
 
        """Produce an rss2 feed via feedgenerator module"""
 

	
 
        @cache_region('long_term', '_get_feed_from_cache')
 
        def _get_feed_from_cache(*_cache_keys):  # parameters are not really used - only as caching key
 
            feed = Rss201rev2Feed(
 
                title=_('%s %s feed') % (c.site_name, repo_name),
 
                link=h.canonical_url('summary_home', repo_name=repo_name),
 
                description=_('Changes on %s repository') % repo_name,
 
                language=language,
 
                ttl=ttl
 
            )
 

	
 
            rss_items_per_page = safe_int(CONFIG.get('rss_items_per_page', 20))
 
            for cs in reversed(list(c.db_repo_scm_instance[-rss_items_per_page:])):
 
                feed.add_item(title=self._get_title(cs),
 
                              link=h.canonical_url('changeset_home', repo_name=repo_name,
 
                                       revision=cs.raw_id),
 
                              author_name=cs.author,
 
                              description=''.join(self.__get_desc(cs)),
 
                              pubdate=cs.date,
 
                             )
 

	
 
            response.content_type = feed.mime_type
 
            return feed.writeString('utf-8')
 

	
 
        kind = 'RSS'
 
        return _get_feed_from_cache(repo_name, kind, c.db_repo.changeset_cache.get('raw_id'))
 
        """Produce a simple rss2 feed"""
 
        return self._feed(repo_name, feeds.RssFeed)
kallithea/controllers/files.py
Show inline comments
 
@@ -49,10 +49,10 @@ from kallithea.lib.utils import action_l
 
from kallithea.lib.utils2 import convert_line_endings, detect_mode, safe_int, safe_str, str2bool
 
from kallithea.lib.vcs.backends.base import EmptyChangeset
 
from kallithea.lib.vcs.conf import settings
 
from kallithea.lib.vcs.exceptions import (
 
    ChangesetDoesNotExistError, ChangesetError, EmptyRepositoryError, ImproperArchiveTypeError, NodeAlreadyExistsError, NodeDoesNotExistError, NodeError, RepositoryError, VCSError)
 
from kallithea.lib.vcs.exceptions import (ChangesetDoesNotExistError, ChangesetError, EmptyRepositoryError, ImproperArchiveTypeError, NodeAlreadyExistsError,
 
                                          NodeDoesNotExistError, NodeError, RepositoryError, VCSError)
 
from kallithea.lib.vcs.nodes import FileNode
 
from kallithea.model.db import Repository
 
from kallithea.model import db
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.scm import ScmModel
 

	
 
@@ -90,7 +90,7 @@ class FilesController(BaseRepoController
 
            h.flash(msg, category='error')
 
            raise HTTPNotFound()
 
        except RepositoryError as e:
 
            h.flash(safe_str(e), category='error')
 
            h.flash(e, category='error')
 
            raise HTTPNotFound()
 

	
 
    def __get_filenode(self, cs, path):
 
@@ -110,7 +110,7 @@ class FilesController(BaseRepoController
 
            h.flash(msg, category='error')
 
            raise HTTPNotFound()
 
        except RepositoryError as e:
 
            h.flash(safe_str(e), category='error')
 
            h.flash(e, category='error')
 
            raise HTTPNotFound()
 

	
 
        return file_node
 
@@ -163,7 +163,7 @@ class FilesController(BaseRepoController
 
                c.load_full_history = False
 
                # determine if we're on branch head
 
                _branches = c.db_repo_scm_instance.branches
 
                c.on_branch_head = revision in _branches.keys() + _branches.values()
 
                c.on_branch_head = revision in _branches or revision in _branches.values()
 
                _hist = []
 
                c.file_history = []
 
                if c.load_full_history:
 
@@ -175,7 +175,7 @@ class FilesController(BaseRepoController
 
            else:
 
                c.authors = c.file_history = []
 
        except RepositoryError as e:
 
            h.flash(safe_str(e), category='error')
 
            h.flash(e, category='error')
 
            raise HTTPNotFound()
 

	
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
@@ -232,8 +232,8 @@ class FilesController(BaseRepoController
 
        cs = self.__get_cs(revision)
 
        file_node = self.__get_filenode(cs, f_path)
 

	
 
        response.content_disposition = 'attachment; filename=%s' % \
 
            safe_str(f_path.split(Repository.url_sep())[-1])
 
        response.content_disposition = \
 
            'attachment; filename=%s' % f_path.split(db.URL_SEP)[-1]
 

	
 
        response.content_type = file_node.mimetype
 
        return file_node.content
 
@@ -277,8 +277,7 @@ class FilesController(BaseRepoController
 
                mimetype, dispo = 'text/plain', 'inline'
 

	
 
        if dispo == 'attachment':
 
            dispo = 'attachment; filename=%s' % \
 
                        safe_str(f_path.split(os.sep)[-1])
 
            dispo = 'attachment; filename=%s' % f_path.split(os.sep)[-1]
 

	
 
        response.content_disposition = dispo
 
        response.content_type = mimetype
 
@@ -292,7 +291,7 @@ class FilesController(BaseRepoController
 
        # create multiple heads via file editing
 
        _branches = repo.scm_instance.branches
 
        # check if revision is a branch name or branch hash
 
        if revision not in _branches.keys() + _branches.values():
 
        if revision not in _branches and revision not in _branches.values():
 
            h.flash(_('You can only delete files with revision '
 
                      'being a valid branch'), category='warning')
 
            raise HTTPFound(location=h.url('files_home',
 
@@ -346,7 +345,7 @@ class FilesController(BaseRepoController
 
        # create multiple heads via file editing
 
        _branches = repo.scm_instance.branches
 
        # check if revision is a branch name or branch hash
 
        if revision not in _branches.keys() + _branches.values():
 
        if revision not in _branches and revision not in _branches.values():
 
            h.flash(_('You can only edit files with revision '
 
                      'being a valid branch'), category='warning')
 
            raise HTTPFound(location=h.url('files_home',
 
@@ -365,8 +364,7 @@ class FilesController(BaseRepoController
 
        c.f_path = f_path
 

	
 
        if r_post:
 

	
 
            old_content = c.file.content
 
            old_content = safe_str(c.file.content)
 
            sl = old_content.splitlines(1)
 
            first_line = sl[0] if sl else ''
 
            # modes:  0 - Unix, 1 - Mac, 2 - DOS
 
@@ -509,8 +507,7 @@ class FilesController(BaseRepoController
 

	
 
        from kallithea import CONFIG
 
        rev_name = cs.raw_id[:12]
 
        archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
 
                                    safe_str(rev_name), ext)
 
        archive_name = '%s-%s%s' % (repo_name.replace('/', '_'), rev_name, ext)
 

	
 
        archive_path = None
 
        cached_archive_path = None
kallithea/controllers/forks.py
Show inline comments
 
@@ -35,13 +35,14 @@ from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPFound
 

	
 
import kallithea
 
import kallithea.lib.helpers as h
 
from kallithea.config.routing import url
 
from kallithea.lib.auth import HasPermissionAny, HasPermissionAnyDecorator, HasRepoPermissionLevel, HasRepoPermissionLevelDecorator, LoginRequired
 
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, Ui, User, UserFollowing
 
from kallithea.model.db import Repository, Ui, UserFollowing
 
from kallithea.model.forms import RepoForkForm
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.scm import AvailableRepoGroupChoices, ScmModel
 
@@ -76,7 +77,7 @@ class ForksController(BaseRepoController
 
            h.not_mapped_error(c.repo_name)
 
            raise HTTPFound(location=url('repos'))
 

	
 
        c.default_user_id = User.get_default_user().user_id
 
        c.default_user_id = kallithea.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()
kallithea/controllers/home.py
Show inline comments
 
@@ -37,7 +37,7 @@ from webob.exc import HTTPBadRequest
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 
from kallithea.lib.base import BaseController, jsonify, render
 
from kallithea.lib.utils import conditional_cache
 
from kallithea.lib.utils2 import safe_str
 
from kallithea.model.db import RepoGroup, Repository, User, UserGroup
 
from kallithea.model.repo import RepoModel
 
from kallithea.model.scm import UserGroupList
 
@@ -67,9 +67,7 @@ class HomeController(BaseController):
 
    @LoginRequired(allow_default_user=True)
 
    @jsonify
 
    def repo_switcher_data(self):
 
        # wrapper for conditional cache
 
        def _c():
 
            log.debug('generating switcher repo/groups list')
 
        if request.is_xhr:
 
            all_repos = Repository.query(sorted=True).all()
 
            repo_iter = self.scm_model.get_repos(all_repos)
 
            all_groups = RepoGroup.query(sorted=True).all()
 
@@ -96,17 +94,16 @@ class HomeController(BaseController):
 
                    ],
 
                   }]
 

	
 
            for res_dict in res:
 
                for child in (res_dict['children']):
 
                    child['obj'].pop('_changeset_cache', None)  # bytes cannot be encoded in json ... but this value isn't relevant on client side at all ...
 

	
 
            data = {
 
                'more': False,
 
                'results': res,
 
            }
 
            return data
 

	
 
        if request.is_xhr:
 
            condition = False
 
            compute = conditional_cache('short_term', 'cache_desc',
 
                                        condition=condition, func=_c)
 
            return compute()
 
        else:
 
            raise HTTPBadRequest()
 

	
 
@@ -120,25 +117,25 @@ class HomeController(BaseController):
 
        if _branches:
 
            res.append({
 
                'text': _('Branch'),
 
                'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches]
 
                'children': [{'id': safe_str(rev), 'text': safe_str(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]
 
                'children': [{'id': safe_str(rev), 'text': safe_str(name), 'type': 'closed-branch'} for name, rev in _closed_branches]
 
            })
 
        _tags = repo.tags.items()
 
        if _tags:
 
            res.append({
 
                'text': _('Tag'),
 
                'children': [{'id': rev, 'text': name, 'type': 'tag'} for name, rev in _tags]
 
                'children': [{'id': safe_str(rev), 'text': safe_str(name), 'type': 'tag'} for name, rev in _tags]
 
            })
 
        _bookmarks = repo.bookmarks.items()
 
        if _bookmarks:
 
            res.append({
 
                'text': _('Bookmark'),
 
                'children': [{'id': rev, 'text': name, 'type': 'book'} for name, rev in _bookmarks]
 
                'children': [{'id': safe_str(rev), 'text': safe_str(name), 'type': 'book'} for name, rev in _bookmarks]
 
            })
 
        data = {
 
            'more': False,
kallithea/controllers/journal.py
Show inline comments
 
@@ -23,7 +23,6 @@ Original author and date, and relevant c
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 

	
 
"""
 

	
 
import logging
 
@@ -35,12 +34,11 @@ from sqlalchemy.orm import joinedload
 
from tg import request, response
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
from webob.exc import HTTPBadRequest
 

	
 
import kallithea.lib.helpers as h
 
from kallithea.config.routing import url
 
from kallithea.controllers.admin.admin import _journal_filter
 
from kallithea.lib import feeds
 
from kallithea.lib.auth import LoginRequired
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.page import Page
 
@@ -105,22 +103,17 @@ class JournalController(BaseController):
 

	
 
        return journal
 

	
 
    def _atom_feed(self, repos, public=True):
 
    def _feed(self, repos, feeder, link, desc):
 
        response.content_type = feeder.content_type
 
        journal = self._get_journal_data(repos)
 
        if public:
 
            _link = h.canonical_url('public_journal_atom')
 
            _desc = '%s %s %s' % (c.site_name, _('Public Journal'),
 
                                  'atom feed')
 
        else:
 
            _link = h.canonical_url('journal_atom')
 
            _desc = '%s %s %s' % (c.site_name, _('Journal'), 'atom feed')
 

	
 
        feed = Atom1Feed(title=_desc,
 
                         link=_link,
 
                         description=_desc,
 
                         language=language,
 
                         ttl=ttl)
 
        header = dict(
 
            title=desc,
 
            link=link,
 
            description=desc,
 
        )
 

	
 
        entries=[]
 
        for entry in journal[:feed_nr]:
 
            user = entry.user
 
            if user is None:
 
@@ -131,63 +124,43 @@ class JournalController(BaseController):
 
            action, action_extra, ico = h.action_parser(entry, feed=True)
 
            title = "%s - %s %s" % (user.short_contact, action(),
 
                                    entry.repository.repo_name)
 
            desc = action_extra()
 
            _url = None
 
            if entry.repository is not None:
 
                _url = h.canonical_url('changelog_home',
 
                           repo_name=entry.repository.repo_name)
 

	
 
            feed.add_item(title=title,
 
            entries.append(dict(
 
                title=title,
 
                          pubdate=entry.action_date,
 
                          link=_url or h.canonical_url(''),
 
                          author_email=user.email,
 
                          author_name=user.full_contact,
 
                          description=desc)
 
                author_name=user.full_name_or_username,
 
                description=action_extra(),
 
            ))
 

	
 
        return feeder.render(header, entries)
 

	
 
        response.content_type = feed.mime_type
 
        return feed.writeString('utf-8')
 
    def _atom_feed(self, repos, public=True):
 
        if public:
 
            link = h.canonical_url('public_journal_atom')
 
            desc = '%s %s %s' % (c.site_name, _('Public Journal'),
 
                                  'atom feed')
 
        else:
 
            link = h.canonical_url('journal_atom')
 
            desc = '%s %s %s' % (c.site_name, _('Journal'), 'atom feed')
 

	
 
        return self._feed(repos, feeds.AtomFeed, link, desc)
 

	
 
    def _rss_feed(self, repos, public=True):
 
        journal = self._get_journal_data(repos)
 
        if public:
 
            _link = h.canonical_url('public_journal_atom')
 
            _desc = '%s %s %s' % (c.site_name, _('Public Journal'),
 
            link = h.canonical_url('public_journal_atom')
 
            desc = '%s %s %s' % (c.site_name, _('Public Journal'),
 
                                  'rss feed')
 
        else:
 
            _link = h.canonical_url('journal_atom')
 
            _desc = '%s %s %s' % (c.site_name, _('Journal'), 'rss feed')
 

	
 
        feed = Rss201rev2Feed(title=_desc,
 
                         link=_link,
 
                         description=_desc,
 
                         language=language,
 
                         ttl=ttl)
 
            link = h.canonical_url('journal_atom')
 
            desc = '%s %s %s' % (c.site_name, _('Journal'), 'rss feed')
 

	
 
        for entry in journal[:feed_nr]:
 
            user = entry.user
 
            if user is None:
 
                # fix deleted users
 
                user = AttributeDict({'short_contact': entry.username,
 
                                      'email': '',
 
                                      'full_contact': ''})
 
            action, action_extra, ico = h.action_parser(entry, feed=True)
 
            title = "%s - %s %s" % (user.short_contact, action(),
 
                                    entry.repository.repo_name)
 
            desc = action_extra()
 
            _url = None
 
            if entry.repository is not None:
 
                _url = h.canonical_url('changelog_home',
 
                           repo_name=entry.repository.repo_name)
 

	
 
            feed.add_item(title=title,
 
                          pubdate=entry.action_date,
 
                          link=_url or h.canonical_url(''),
 
                          author_email=user.email,
 
                          author_name=user.full_contact,
 
                          description=desc)
 

	
 
        response.content_type = feed.mime_type
 
        return feed.writeString('utf-8')
 
        return self._feed(repos, feeds.RssFeed, link, desc)
 

	
 
    @LoginRequired()
 
    def index(self):
 
@@ -201,10 +174,8 @@ class JournalController(BaseController):
 

	
 
        journal = self._get_journal_data(c.following)
 

	
 
        def url_generator(**kw):
 
            return url.current(filter=c.search_term, **kw)
 

	
 
        c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
 
        c.journal_pager = Page(journal, page=p, items_per_page=20,
 
                               filter=c.search_term)
 
        c.journal_day_aggregate = self._get_daily_aggregate(c.journal_pager)
 

	
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
@@ -221,9 +192,7 @@ class JournalController(BaseController):
 

	
 
    @LoginRequired()
 
    def journal_atom(self):
 
        """
 
        Produce an atom-1.0 feed via feedgenerator module
 
        """
 
        """Produce a simple atom-1.0 feed"""
 
        following = UserFollowing.query() \
 
            .filter(UserFollowing.user_id == request.authuser.user_id) \
 
            .options(joinedload(UserFollowing.follows_repository)) \
 
@@ -232,9 +201,7 @@ class JournalController(BaseController):
 

	
 
    @LoginRequired()
 
    def journal_rss(self):
 
        """
 
        Produce an rss feed via feedgenerator module
 
        """
 
        """Produce a simple rss2 feed"""
 
        following = UserFollowing.query() \
 
            .filter(UserFollowing.user_id == request.authuser.user_id) \
 
            .options(joinedload(UserFollowing.follows_repository)) \
 
@@ -290,9 +257,7 @@ class JournalController(BaseController):
 

	
 
    @LoginRequired(allow_default_user=True)
 
    def public_journal_atom(self):
 
        """
 
        Produce an atom-1.0 feed via feedgenerator module
 
        """
 
        """Produce a simple atom-1.0 feed"""
 
        c.following = UserFollowing.query() \
 
            .filter(UserFollowing.user_id == request.authuser.user_id) \
 
            .options(joinedload(UserFollowing.follows_repository)) \
 
@@ -302,9 +267,7 @@ class JournalController(BaseController):
 

	
 
    @LoginRequired(allow_default_user=True)
 
    def public_journal_rss(self):
 
        """
 
        Produce an rss2 feed via feedgenerator module
 
        """
 
        """Produce a simple rss2 feed"""
 
        c.following = UserFollowing.query() \
 
            .filter(UserFollowing.user_id == request.authuser.user_id) \
 
            .options(joinedload(UserFollowing.follows_repository)) \
kallithea/controllers/login.py
Show inline comments
 
@@ -41,7 +41,6 @@ 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
 
from kallithea.lib.utils2 import safe_str
 
from kallithea.model.db import Setting, User
 
from kallithea.model.forms import LoginForm, PasswordResetConfirmationForm, PasswordResetRequestForm, RegisterForm
 
from kallithea.model.meta import Session
 
@@ -68,7 +67,7 @@ class LoginController(BaseController):
 
        return _re.match(came_from) is not None
 

	
 
    def index(self):
 
        c.came_from = safe_str(request.GET.get('came_from', ''))
 
        c.came_from = request.GET.get('came_from', '')
 
        if c.came_from:
 
            if not self._validate_came_from(c.came_from):
 
                log.error('Invalid came_from (not server-relative): %r', c.came_from)
 
@@ -80,10 +79,11 @@ class LoginController(BaseController):
 
            # import Login Form validator class
 
            login_form = LoginForm()()
 
            try:
 
                # login_form will check username/password using ValidAuth and report failure to the user
 
                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_or_email(username, case_insensitive=True)
 
                user = User.get_by_username_or_email(username)
 
                assert user is not None  # the same user get just passed in the form validation
 
            except formencode.Invalid as errors:
 
                defaults = errors.value
 
                # remove password from filling in form again
 
@@ -102,9 +102,11 @@ class LoginController(BaseController):
 
                # Exception itself
 
                h.flash(e, 'error')
 
            else:
 
                # login_form already validated the password - now set the session cookie accordingly
 
                auth_user = log_in_user(user, c.form_result['remember'], is_external_auth=False, ip_addr=request.ip_addr)
 
                # TODO: handle auth_user is None as failed authentication?
 
                if auth_user:
 
                raise HTTPFound(location=c.came_from)
 
                h.flash(_('Authentication failed.'), 'error')
 
        else:
 
            # redirect if already logged in
 
            if not request.authuser.is_anonymous:
kallithea/controllers/pullrequests.py
Show inline comments
 
@@ -29,6 +29,7 @@ import logging
 
import traceback
 

	
 
import formencode
 
import mercurial.unionrepo
 
from tg import request
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
@@ -42,10 +43,8 @@ from kallithea.lib.auth import HasRepoPe
 
from kallithea.lib.base import BaseRepoController, jsonify, render
 
from kallithea.lib.graphmod import graph_data
 
from kallithea.lib.page import Page
 
from kallithea.lib.utils2 import safe_int
 
from kallithea.lib.utils2 import ascii_bytes, safe_bytes, safe_int
 
from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError
 
from kallithea.lib.vcs.utils import safe_str
 
from kallithea.lib.vcs.utils.hgcompat import unionrepo
 
from kallithea.model.changeset_status import ChangesetStatusModel
 
from kallithea.model.comment import ChangesetCommentsModel
 
from kallithea.model.db import ChangesetStatus, PullRequest, PullRequestReviewer, Repository, User
 
@@ -83,22 +82,15 @@ class PullrequestsController(BaseRepoCon
 
        # list named branches that has been merged to this named branch - it should probably merge back
 
        peers = []
 

	
 
        if rev:
 
            rev = safe_str(rev)
 

	
 
        if branch:
 
            branch = safe_str(branch)
 

	
 
        if branch_rev:
 
            branch_rev = safe_str(branch_rev)
 
            # a revset not restricting to merge() would be better
 
            # (especially because it would get the branch point)
 
            # ... but is currently too expensive
 
            # including branches of children could be nice too
 
            peerbranches = set()
 
            for i in repo._repo.revs(
 
                "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
 
                branch_rev, branch_rev
 
                b"sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
 
                ascii_bytes(branch_rev), ascii_bytes(branch_rev),
 
            ):
 
                for abranch in repo.get_changeset(i).branches:
 
                    if abranch not in peerbranches:
 
@@ -111,7 +103,7 @@ class PullrequestsController(BaseRepoCon
 
        tipbranch = None
 

	
 
        branches = []
 
        for abranch, branchrev in repo.branches.iteritems():
 
        for abranch, branchrev in repo.branches.items():
 
            n = 'branch:%s:%s' % (abranch, branchrev)
 
            desc = abranch
 
            if branchrev == tiprev:
 
@@ -135,14 +127,14 @@ class PullrequestsController(BaseRepoCon
 
                log.debug('branch %r not found in %s', branch, repo)
 

	
 
        bookmarks = []
 
        for bookmark, bookmarkrev in repo.bookmarks.iteritems():
 
        for bookmark, bookmarkrev in repo.bookmarks.items():
 
            n = 'book:%s:%s' % (bookmark, bookmarkrev)
 
            bookmarks.append((n, bookmark))
 
            if rev == bookmarkrev:
 
                selected = n
 

	
 
        tags = []
 
        for tag, tagrev in repo.tags.iteritems():
 
        for tag, tagrev in repo.tags.items():
 
            if tag == 'tip':
 
                continue
 
            n = 'tag:%s:%s' % (tag, tagrev)
 
@@ -173,7 +165,7 @@ class PullrequestsController(BaseRepoCon
 
                if 'master' in repo.branches:
 
                    selected = 'branch:master:%s' % repo.branches['master']
 
                else:
 
                    k, v = repo.branches.items()[0]
 
                    k, v = list(repo.branches.items())[0]
 
                    selected = 'branch:%s:%s' % (k, v)
 

	
 
        groups = [(specials, _("Special")),
 
@@ -201,6 +193,11 @@ class PullrequestsController(BaseRepoCon
 
    def show_all(self, repo_name):
 
        c.from_ = request.GET.get('from_') or ''
 
        c.closed = request.GET.get('closed') or ''
 
        url_params = {}
 
        if c.from_:
 
            url_params['from_'] = 1
 
        if c.closed:
 
            url_params['closed'] = 1
 
        p = safe_int(request.GET.get('page'), 1)
 

	
 
        q = PullRequest.query(include_closed=c.closed, sorted=True)
 
@@ -210,7 +207,7 @@ class PullrequestsController(BaseRepoCon
 
            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)
 
        c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100, **url_params)
 

	
 
        return render('/pullrequests/pullrequest_show_all.html')
 

	
 
@@ -335,7 +332,7 @@ class PullrequestsController(BaseRepoCon
 
        try:
 
            cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
 
        except CreatePullRequestAction.ValidationError as e:
 
            h.flash(str(e), category='error', logf=log.error)
 
            h.flash(e, category='error', logf=log.error)
 
            raise HTTPNotFound
 

	
 
        try:
 
@@ -358,7 +355,7 @@ class PullrequestsController(BaseRepoCon
 
        try:
 
            cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
 
        except CreatePullRequestAction.ValidationError as e:
 
            h.flash(str(e), category='error', logf=log.error)
 
            h.flash(e, category='error', logf=log.error)
 
            raise HTTPNotFound
 

	
 
        try:
 
@@ -531,14 +528,9 @@ class PullrequestsController(BaseRepoCon
 
                            # 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
 
                            try:
 
                                hgrepo = unionrepo.makeunionrepository(org_scm_instance.baseui,
 
                                                                       org_scm_instance.path,
 
                                                                       other_scm_instance.path)
 
                            except AttributeError: # makeunionrepository was introduced in Mercurial 4.8 23f2299e9e53
 
                                hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
 
                                                                   org_scm_instance.path,
 
                                                                   other_scm_instance.path)
 
                            hgrepo = mercurial.unionrepo.makeunionrepository(org_scm_instance.baseui,
 
                                                                   safe_bytes(org_scm_instance.path),
 
                                                                   safe_bytes(other_scm_instance.path))
 
                        else:
 
                            hgrepo = org_scm_instance._repo
 
                        show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
 
@@ -588,11 +580,11 @@ class PullrequestsController(BaseRepoCon
 
        log.debug('running diff between %s and %s in %s',
 
                  c.a_rev, c.cs_rev, org_scm_instance.path)
 
        try:
 
            raw_diff = diffs.get_diff(org_scm_instance, rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
 
            raw_diff = diffs.get_diff(org_scm_instance, rev1=c.a_rev, rev2=c.cs_rev,
 
                                      ignore_whitespace=ignore_whitespace, context=line_context)
 
        except ChangesetDoesNotExistError:
 
            raw_diff = _("The diff can't be shown - the PR revisions could not be found.")
 
        diff_processor = diffs.DiffProcessor(raw_diff or '', diff_limit=diff_limit)
 
            raw_diff = safe_bytes(_("The diff can't be shown - the PR revisions could not be found."))
 
        diff_processor = diffs.DiffProcessor(raw_diff, diff_limit=diff_limit)
 
        c.limited_diff = diff_processor.limited_diff
 
        c.file_diff_data = []
 
        c.lines_added = 0
kallithea/controllers/search.py
Show inline comments
 
@@ -27,12 +27,10 @@ Original author and date, and relevant c
 

	
 
import logging
 
import traceback
 
import urllib
 

	
 
from tg import config, request
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webhelpers2.html.tools import update_params
 
from whoosh.index import EmptyIndexError, exists_in, open_dir
 
from whoosh.qparser import QueryParser, QueryParserError
 
from whoosh.query import Phrase, Prefix
 
@@ -41,7 +39,7 @@ from kallithea.lib.auth import LoginRequ
 
from kallithea.lib.base import BaseRepoController, render
 
from kallithea.lib.indexers import CHGSET_IDX_NAME, CHGSETS_SCHEMA, IDX_NAME, SCHEMA, WhooshResultWrapper
 
from kallithea.lib.page import Page
 
from kallithea.lib.utils2 import safe_int, safe_str
 
from kallithea.lib.utils2 import safe_int
 
from kallithea.model.repo import RepoModel
 

	
 

	
 
@@ -96,9 +94,9 @@ class SearchController(BaseRepoControlle
 
                if c.repo_name:
 
                    # use "repository_rawname:" instead of "repository:"
 
                    # for case-sensitive matching
 
                    cur_query = u'repository_rawname:%s %s' % (c.repo_name, cur_query)
 
                    cur_query = 'repository_rawname:%s %s' % (c.repo_name, cur_query)
 
                try:
 
                    query = qp.parse(unicode(cur_query))
 
                    query = qp.parse(cur_query)
 
                    # extract words for highlight
 
                    if isinstance(query, Phrase):
 
                        highlight_items.update(query.words)
 
@@ -119,9 +117,6 @@ class SearchController(BaseRepoControlle
 
                        res_ln, results.runtime
 
                    )
 

	
 
                    def url_generator(**kw):
 
                        q = urllib.quote(safe_str(c.cur_query))
 
                        return update_params("?q=%s&type=%s" % (q, safe_str(c.cur_type)), **kw)
 
                    repo_location = RepoModel().repos_path
 
                    c.formated_results = Page(
 
                        WhooshResultWrapper(search_type, searcher, matcher,
 
@@ -129,7 +124,8 @@ class SearchController(BaseRepoControlle
 
                        page=p,
 
                        item_count=res_ln,
 
                        items_per_page=10,
 
                        url=url_generator
 
                        type=c.cur_type,
 
                        q=c.cur_query,
 
                    )
 

	
 
                except QueryParserError:
kallithea/controllers/summary.py
Show inline comments
 
@@ -38,14 +38,15 @@ from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
from webob.exc import HTTPBadRequest
 

	
 
import kallithea.lib.helpers as h
 
from kallithea.config.conf import ALL_EXTS, ALL_READMES, LANGUAGES_EXTENSIONS_MAP
 
from kallithea.lib import ext_json
 
from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 
from kallithea.lib.base import BaseRepoController, jsonify, render
 
from kallithea.lib.celerylib.tasks import get_commits_stats
 
from kallithea.lib.compat import json
 
from kallithea.lib.markup_renderer import MarkupRenderer
 
from kallithea.lib.page import RepoPage
 
from kallithea.lib.utils2 import safe_int
 
from kallithea.lib.page import Page
 
from kallithea.lib.utils2 import safe_int, safe_str
 
from kallithea.lib.vcs.backends.base import EmptyChangeset
 
from kallithea.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, NodeDoesNotExistError
 
from kallithea.lib.vcs.nodes import FileNode
 
@@ -65,7 +66,7 @@ class SummaryController(BaseRepoControll
 
        repo_name = db_repo.repo_name
 
        log.debug('Looking for README file')
 

	
 
        @cache_region('long_term', '_get_readme_from_cache')
 
        @cache_region('long_term_file', '_get_readme_from_cache')
 
        def _get_readme_from_cache(*_cache_keys):  # parameters are not really used - only as caching key
 
            readme_data = None
 
            readme_file = None
 
@@ -83,7 +84,7 @@ class SummaryController(BaseRepoControll
 
                        readme_file = f
 
                        log.debug('Found README file `%s` rendering...',
 
                                  readme_file)
 
                        readme_data = renderer.render(readme.content,
 
                        readme_data = renderer.render(safe_str(readme.content),
 
                                                      filename=f)
 
                        break
 
                    except NodeDoesNotExistError:
 
@@ -104,8 +105,12 @@ class SummaryController(BaseRepoControll
 
    def index(self, repo_name):
 
        p = safe_int(request.GET.get('page'), 1)
 
        size = safe_int(request.GET.get('size'), 10)
 
        collection = c.db_repo_scm_instance
 
        c.cs_pagination = RepoPage(collection, page=p, items_per_page=size)
 
        try:
 
            collection = c.db_repo_scm_instance.get_changesets(reverse=True)
 
        except EmptyRepositoryError as e:
 
            h.flash(e, category='warning')
 
            collection = []
 
        c.cs_pagination = Page(collection, page=p, items_per_page=size)
 
        page_revisions = [x.raw_id for x in list(c.cs_pagination)]
 
        c.cs_comments = c.db_repo.get_comments(page_revisions)
 
        c.cs_statuses = c.db_repo.statuses(page_revisions)
 
@@ -133,17 +138,13 @@ class SummaryController(BaseRepoControll
 
        c.stats_percentage = 0
 

	
 
        if stats and stats.languages:
 
            c.no_data = False is c.db_repo.enable_statistics
 
            lang_stats_d = json.loads(stats.languages)
 

	
 
            lang_stats_d = ext_json.loads(stats.languages)
 
            lang_stats = [(x, {"count": y,
 
                               "desc": LANGUAGES_EXTENSIONS_MAP.get(x, '?')})
 
                          for x, y in lang_stats_d.items()]
 
            lang_stats.sort(key=lambda k: (-k[1]['count'], k[0]))
 

	
 
            c.trending_languages = lang_stats[:10]
 
        else:
 
            c.no_data = True
 
            c.trending_languages = []
 

	
 
        c.enable_downloads = c.db_repo.enable_downloads
 
@@ -171,7 +172,7 @@ class SummaryController(BaseRepoControll
 
            c.no_data_msg = _('Statistics are disabled for this repository')
 

	
 
        td = date.today() + timedelta(days=1)
 
        td_1m = td - timedelta(days=calendar.mdays[td.month])
 
        td_1m = td - timedelta(days=calendar.monthrange(td.year, td.month)[1])
 
        td_1y = td - timedelta(days=365)
 

	
 
        ts_min_m = mktime(td_1m.timetuple())
 
@@ -185,18 +186,16 @@ class SummaryController(BaseRepoControll
 
            .scalar()
 
        c.stats_percentage = 0
 
        if stats and stats.languages:
 
            c.no_data = False is c.db_repo.enable_statistics
 
            lang_stats_d = json.loads(stats.languages)
 
            c.commit_data = json.loads(stats.commit_activity)
 
            c.overview_data = json.loads(stats.commit_activity_combined)
 
            c.commit_data = ext_json.loads(stats.commit_activity)
 
            c.overview_data = ext_json.loads(stats.commit_activity_combined)
 

	
 
            lang_stats = ((x, {"count": y,
 
                               "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
 
                          for x, y in lang_stats_d.items())
 
            lang_stats_d = ext_json.loads(stats.languages)
 
            lang_stats = [(x, {"count": y,
 
                               "desc": LANGUAGES_EXTENSIONS_MAP.get(x, '?')})
 
                          for x, y in lang_stats_d.items()]
 
            lang_stats.sort(key=lambda k: (-k[1]['count'], k[0]))
 
            c.trending_languages = lang_stats[:10]
 

	
 
            c.trending_languages = (
 
                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() \
 
                if c.db_repo_scm_instance.revisions else 0
 
@@ -208,8 +207,7 @@ class SummaryController(BaseRepoControll
 
        else:
 
            c.commit_data = {}
 
            c.overview_data = ([[ts_min_y, 0], [ts_max_y, 10]])
 
            c.trending_languages = {}
 
            c.no_data = True
 
            c.trending_languages = []
 

	
 
        recurse_limit = 500  # don't recurse more than 500 times when parsing
 
        get_commits_stats(c.db_repo.repo_name, ts_min_y, ts_max_y, recurse_limit)
kallithea/front-end/package-lock.json
Show inline comments
 
@@ -3,18 +3,49 @@
 
  "requires": true,
 
  "lockfileVersion": 1,
 
  "dependencies": {
 
    "@babel/code-frame": {
 
      "version": "7.8.3",
 
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
 
      "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
 
      "dev": true,
 
      "requires": {
 
        "@babel/highlight": "^7.8.3"
 
      }
 
    },
 
    "@babel/highlight": {
 
      "version": "7.8.3",
 
      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz",
 
      "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==",
 
      "dev": true,
 
      "requires": {
 
        "chalk": "^2.0.0",
 
        "esutils": "^2.0.2",
 
        "js-tokens": "^4.0.0"
 
      }
 
    },
 
    "abbrev": {
 
      "version": "1.1.1",
 
      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
 
      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
 
      "dev": true
 
    },
 
    "acorn": {
 
      "version": "7.1.0",
 
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
 
      "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
 
      "dev": true
 
    },
 
    "acorn-jsx": {
 
      "version": "5.1.0",
 
      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz",
 
      "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==",
 
      "dev": true
 
    },
 
    "ajv": {
 
      "version": "6.10.2",
 
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
 
      "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
 
      "dev": true,
 
      "optional": true,
 
      "requires": {
 
        "fast-deep-equal": "^2.0.1",
 
        "fast-json-stable-stringify": "^2.0.0",
 
@@ -28,6 +59,21 @@
 
      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
 
      "dev": true
 
    },
 
    "ansi-escapes": {
 
      "version": "4.3.0",
 
      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz",
 
      "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==",
 
      "dev": true,
 
      "requires": {
 
        "type-fest": "^0.8.1"
 
      }
 
    },
 
    "ansi-regex": {
 
      "version": "5.0.0",
 
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
 
      "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
 
      "dev": true
 
    },
 
    "ansi-styles": {
 
      "version": "3.2.1",
 
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
 
@@ -37,6 +83,15 @@
 
        "color-convert": "^1.9.0"
 
      }
 
    },
 
    "argparse": {
 
      "version": "1.0.10",
 
      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
 
      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
 
      "dev": true,
 
      "requires": {
 
        "sprintf-js": "~1.0.2"
 
      }
 
    },
 
    "array-find-index": {
 
      "version": "1.0.2",
 
      "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
 
@@ -66,6 +121,12 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "astral-regex": {
 
      "version": "1.0.0",
 
      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
 
      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
 
      "dev": true
 
    },
 
    "asynckit": {
 
      "version": "0.4.0",
 
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
 
@@ -123,6 +184,12 @@
 
        "concat-map": "0.0.1"
 
      }
 
    },
 
    "callsites": {
 
      "version": "3.1.0",
 
      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
 
      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
 
      "dev": true
 
    },
 
    "caseless": {
 
      "version": "0.12.0",
 
      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
 
@@ -141,6 +208,12 @@
 
        "supports-color": "^5.3.0"
 
      }
 
    },
 
    "chardet": {
 
      "version": "0.7.0",
 
      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
 
      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
 
      "dev": true
 
    },
 
    "clean-css": {
 
      "version": "3.4.28",
 
      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz",
 
@@ -162,6 +235,21 @@
 
        }
 
      }
 
    },
 
    "cli-cursor": {
 
      "version": "3.1.0",
 
      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
 
      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
 
      "dev": true,
 
      "requires": {
 
        "restore-cursor": "^3.1.0"
 
      }
 
    },
 
    "cli-width": {
 
      "version": "2.2.0",
 
      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
 
      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
 
      "dev": true
 
    },
 
    "clone": {
 
      "version": "2.1.2",
 
      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
 
@@ -220,6 +308,19 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "cross-spawn": {
 
      "version": "6.0.5",
 
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
 
      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
 
      "dev": true,
 
      "requires": {
 
        "nice-try": "^1.0.4",
 
        "path-key": "^2.0.1",
 
        "semver": "^5.5.0",
 
        "shebang-command": "^1.2.0",
 
        "which": "^1.2.9"
 
      }
 
    },
 
    "dashdash": {
 
      "version": "1.14.1",
 
      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
 
@@ -262,6 +363,12 @@
 
      "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=",
 
      "dev": true
 
    },
 
    "deep-is": {
 
      "version": "0.1.3",
 
      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
 
      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
 
      "dev": true
 
    },
 
    "delayed-stream": {
 
      "version": "1.0.0",
 
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
 
@@ -279,6 +386,64 @@
 
        "wrappy": "1"
 
      }
 
    },
 
    "doctrine": {
 
      "version": "3.0.0",
 
      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
 
      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
 
      "dev": true,
 
      "requires": {
 
        "esutils": "^2.0.2"
 
      }
 
    },
 
    "dom-serializer": {
 
      "version": "0.2.2",
 
      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
 
      "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
 
      "dev": true,
 
      "requires": {
 
        "domelementtype": "^2.0.1",
 
        "entities": "^2.0.0"
 
      },
 
      "dependencies": {
 
        "domelementtype": {
 
          "version": "2.0.1",
 
          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
 
          "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
 
          "dev": true
 
        },
 
        "entities": {
 
          "version": "2.0.0",
 
          "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
 
          "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
 
          "dev": true
 
        }
 
      }
 
    },
 
    "domelementtype": {
 
      "version": "1.3.1",
 
      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
 
      "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
 
      "dev": true
 
    },
 
    "domhandler": {
 
      "version": "2.4.2",
 
      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
 
      "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
 
      "dev": true,
 
      "requires": {
 
        "domelementtype": "1"
 
      }
 
    },
 
    "domutils": {
 
      "version": "1.7.0",
 
      "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
 
      "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
 
      "dev": true,
 
      "requires": {
 
        "dom-serializer": "0",
 
        "domelementtype": "1"
 
      }
 
    },
 
    "ecc-jsbn": {
 
      "version": "0.1.2",
 
      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
 
@@ -290,6 +455,18 @@
 
        "safer-buffer": "^2.1.0"
 
      }
 
    },
 
    "emoji-regex": {
 
      "version": "8.0.0",
 
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 
      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
 
      "dev": true
 
    },
 
    "entities": {
 
      "version": "1.1.2",
 
      "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
 
      "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
 
      "dev": true
 
    },
 
    "errno": {
 
      "version": "0.1.7",
 
      "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
 
@@ -306,6 +483,149 @@
 
      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
 
      "dev": true
 
    },
 
    "eslint": {
 
      "version": "6.8.0",
 
      "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
 
      "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
 
      "dev": true,
 
      "requires": {
 
        "@babel/code-frame": "^7.0.0",
 
        "ajv": "^6.10.0",
 
        "chalk": "^2.1.0",
 
        "cross-spawn": "^6.0.5",
 
        "debug": "^4.0.1",
 
        "doctrine": "^3.0.0",
 
        "eslint-scope": "^5.0.0",
 
        "eslint-utils": "^1.4.3",
 
        "eslint-visitor-keys": "^1.1.0",
 
        "espree": "^6.1.2",
 
        "esquery": "^1.0.1",
 
        "esutils": "^2.0.2",
 
        "file-entry-cache": "^5.0.1",
 
        "functional-red-black-tree": "^1.0.1",
 
        "glob-parent": "^5.0.0",
 
        "globals": "^12.1.0",
 
        "ignore": "^4.0.6",
 
        "import-fresh": "^3.0.0",
 
        "imurmurhash": "^0.1.4",
 
        "inquirer": "^7.0.0",
 
        "is-glob": "^4.0.0",
 
        "js-yaml": "^3.13.1",
 
        "json-stable-stringify-without-jsonify": "^1.0.1",
 
        "levn": "^0.3.0",
 
        "lodash": "^4.17.14",
 
        "minimatch": "^3.0.4",
 
        "mkdirp": "^0.5.1",
 
        "natural-compare": "^1.4.0",
 
        "optionator": "^0.8.3",
 
        "progress": "^2.0.0",
 
        "regexpp": "^2.0.1",
 
        "semver": "^6.1.2",
 
        "strip-ansi": "^5.2.0",
 
        "strip-json-comments": "^3.0.1",
 
        "table": "^5.2.3",
 
        "text-table": "^0.2.0",
 
        "v8-compile-cache": "^2.0.3"
 
      },
 
      "dependencies": {
 
        "debug": {
 
          "version": "4.1.1",
 
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
 
          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
 
          "dev": true,
 
          "requires": {
 
            "ms": "^2.1.1"
 
          }
 
        },
 
        "semver": {
 
          "version": "6.3.0",
 
          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
 
          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
 
          "dev": true
 
        }
 
      }
 
    },
 
    "eslint-plugin-html": {
 
      "version": "6.0.0",
 
      "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.0.0.tgz",
 
      "integrity": "sha512-PQcGippOHS+HTbQCStmH5MY1BF2MaU8qW/+Mvo/8xTa/ioeMXdSP+IiaBw2+nh0KEMfYQKuTz1Zo+vHynjwhbg==",
 
      "dev": true,
 
      "requires": {
 
        "htmlparser2": "^3.10.1"
 
      }
 
    },
 
    "eslint-scope": {
 
      "version": "5.0.0",
 
      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
 
      "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
 
      "dev": true,
 
      "requires": {
 
        "esrecurse": "^4.1.0",
 
        "estraverse": "^4.1.1"
 
      }
 
    },
 
    "eslint-utils": {
 
      "version": "1.4.3",
 
      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
 
      "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
 
      "dev": true,
 
      "requires": {
 
        "eslint-visitor-keys": "^1.1.0"
 
      }
 
    },
 
    "eslint-visitor-keys": {
 
      "version": "1.1.0",
 
      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
 
      "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
 
      "dev": true
 
    },
 
    "espree": {
 
      "version": "6.1.2",
 
      "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz",
 
      "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==",
 
      "dev": true,
 
      "requires": {
 
        "acorn": "^7.1.0",
 
        "acorn-jsx": "^5.1.0",
 
        "eslint-visitor-keys": "^1.1.0"
 
      }
 
    },
 
    "esprima": {
 
      "version": "4.0.1",
 
      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
 
      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
 
      "dev": true
 
    },
 
    "esquery": {
 
      "version": "1.1.0",
 
      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz",
 
      "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==",
 
      "dev": true,
 
      "requires": {
 
        "estraverse": "^4.0.0"
 
      }
 
    },
 
    "esrecurse": {
 
      "version": "4.2.1",
 
      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
 
      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
 
      "dev": true,
 
      "requires": {
 
        "estraverse": "^4.1.0"
 
      }
 
    },
 
    "estraverse": {
 
      "version": "4.3.0",
 
      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
 
      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
 
      "dev": true
 
    },
 
    "esutils": {
 
      "version": "2.0.3",
 
      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
 
      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
 
      "dev": true
 
    },
 
    "extend": {
 
      "version": "3.0.2",
 
      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
 
@@ -313,6 +633,17 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "external-editor": {
 
      "version": "3.1.0",
 
      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
 
      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
 
      "dev": true,
 
      "requires": {
 
        "chardet": "^0.7.0",
 
        "iconv-lite": "^0.4.24",
 
        "tmp": "^0.0.33"
 
      }
 
    },
 
    "extsprintf": {
 
      "version": "1.3.0",
 
      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
 
@@ -324,15 +655,54 @@
 
      "version": "2.0.1",
 
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
 
      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
 
      "dev": true,
 
      "optional": true
 
      "dev": true
 
    },
 
    "fast-json-stable-stringify": {
 
      "version": "2.0.0",
 
      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
 
      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
 
      "dev": true
 
    },
 
    "fast-levenshtein": {
 
      "version": "2.0.6",
 
      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
 
      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
 
      "dev": true
 
    },
 
    "figures": {
 
      "version": "3.2.0",
 
      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
 
      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
 
      "dev": true,
 
      "optional": true
 
      "requires": {
 
        "escape-string-regexp": "^1.0.5"
 
      }
 
    },
 
    "file-entry-cache": {
 
      "version": "5.0.1",
 
      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
 
      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
 
      "dev": true,
 
      "requires": {
 
        "flat-cache": "^2.0.1"
 
      }
 
    },
 
    "flat-cache": {
 
      "version": "2.0.1",
 
      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
 
      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
 
      "dev": true,
 
      "requires": {
 
        "flatted": "^2.0.0",
 
        "rimraf": "2.6.3",
 
        "write": "1.0.3"
 
      }
 
    },
 
    "flatted": {
 
      "version": "2.0.1",
 
      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
 
      "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
 
      "dev": true
 
    },
 
    "forever-agent": {
 
      "version": "0.6.1",
 
@@ -359,6 +729,12 @@
 
      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
 
      "dev": true
 
    },
 
    "functional-red-black-tree": {
 
      "version": "1.0.1",
 
      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
 
      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
 
      "dev": true
 
    },
 
    "getpass": {
 
      "version": "0.1.7",
 
      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
 
@@ -383,6 +759,24 @@
 
        "path-is-absolute": "^1.0.0"
 
      }
 
    },
 
    "glob-parent": {
 
      "version": "5.1.0",
 
      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
 
      "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
 
      "dev": true,
 
      "requires": {
 
        "is-glob": "^4.0.1"
 
      }
 
    },
 
    "globals": {
 
      "version": "12.3.0",
 
      "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz",
 
      "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==",
 
      "dev": true,
 
      "requires": {
 
        "type-fest": "^0.8.1"
 
      }
 
    },
 
    "graceful-fs": {
 
      "version": "4.2.3",
 
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
 
@@ -425,6 +819,20 @@
 
      "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
 
      "dev": true
 
    },
 
    "htmlparser2": {
 
      "version": "3.10.1",
 
      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
 
      "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
 
      "dev": true,
 
      "requires": {
 
        "domelementtype": "^1.3.1",
 
        "domhandler": "^2.3.0",
 
        "domutils": "^1.5.1",
 
        "entities": "^1.1.1",
 
        "inherits": "^2.0.1",
 
        "readable-stream": "^3.1.1"
 
      }
 
    },
 
    "http-signature": {
 
      "version": "1.2.0",
 
      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
 
@@ -437,6 +845,21 @@
 
        "sshpk": "^1.7.0"
 
      }
 
    },
 
    "iconv-lite": {
 
      "version": "0.4.24",
 
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
 
      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
 
      "dev": true,
 
      "requires": {
 
        "safer-buffer": ">= 2.1.2 < 3"
 
      }
 
    },
 
    "ignore": {
 
      "version": "4.0.6",
 
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
 
      "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
 
      "dev": true
 
    },
 
    "image-size": {
 
      "version": "0.5.5",
 
      "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
 
@@ -444,6 +867,22 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "import-fresh": {
 
      "version": "3.2.1",
 
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
 
      "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
 
      "dev": true,
 
      "requires": {
 
        "parent-module": "^1.0.0",
 
        "resolve-from": "^4.0.0"
 
      }
 
    },
 
    "imurmurhash": {
 
      "version": "0.1.4",
 
      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
 
      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
 
      "dev": true
 
    },
 
    "inflight": {
 
      "version": "1.0.6",
 
      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
 
@@ -460,6 +899,54 @@
 
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
 
      "dev": true
 
    },
 
    "inquirer": {
 
      "version": "7.0.4",
 
      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz",
 
      "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==",
 
      "dev": true,
 
      "requires": {
 
        "ansi-escapes": "^4.2.1",
 
        "chalk": "^2.4.2",
 
        "cli-cursor": "^3.1.0",
 
        "cli-width": "^2.0.0",
 
        "external-editor": "^3.0.3",
 
        "figures": "^3.0.0",
 
        "lodash": "^4.17.15",
 
        "mute-stream": "0.0.8",
 
        "run-async": "^2.2.0",
 
        "rxjs": "^6.5.3",
 
        "string-width": "^4.1.0",
 
        "strip-ansi": "^5.1.0",
 
        "through": "^2.3.6"
 
      }
 
    },
 
    "is-extglob": {
 
      "version": "2.1.1",
 
      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
 
      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
 
      "dev": true
 
    },
 
    "is-fullwidth-code-point": {
 
      "version": "3.0.0",
 
      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
 
      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
 
      "dev": true
 
    },
 
    "is-glob": {
 
      "version": "4.0.1",
 
      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
 
      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
 
      "dev": true,
 
      "requires": {
 
        "is-extglob": "^2.1.1"
 
      }
 
    },
 
    "is-promise": {
 
      "version": "2.1.0",
 
      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
 
      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
 
      "dev": true
 
    },
 
    "is-typedarray": {
 
      "version": "1.0.0",
 
      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
 
@@ -467,6 +954,12 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "isexe": {
 
      "version": "2.0.0",
 
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
 
      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
 
      "dev": true
 
    },
 
    "isstream": {
 
      "version": "0.1.2",
 
      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
 
@@ -489,6 +982,22 @@
 
      "resolved": "https://registry.npmjs.org/jquery.flot/-/jquery.flot-0.8.3.tgz",
 
      "integrity": "sha512-/tEE8J5NjwvStHDaCHkvTJpD7wDS4hE1OEL8xEmhgQfUe0gLUem923PIceNez1mz4yBNx6Hjv7pJcowLNd+nbg=="
 
    },
 
    "js-tokens": {
 
      "version": "4.0.0",
 
      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
 
      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
 
      "dev": true
 
    },
 
    "js-yaml": {
 
      "version": "3.13.1",
 
      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
 
      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
 
      "dev": true,
 
      "requires": {
 
        "argparse": "^1.0.7",
 
        "esprima": "^4.0.0"
 
      }
 
    },
 
    "jsbn": {
 
      "version": "0.1.1",
 
      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
 
@@ -513,8 +1022,13 @@
 
      "version": "0.4.1",
 
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
 
      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
 
      "dev": true,
 
      "optional": true
 
      "dev": true
 
    },
 
    "json-stable-stringify-without-jsonify": {
 
      "version": "1.0.1",
 
      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
 
      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
 
      "dev": true
 
    },
 
    "json-stringify-safe": {
 
      "version": "5.0.1",
 
@@ -562,6 +1076,16 @@
 
        "clean-css": "^3.0.1"
 
      }
 
    },
 
    "levn": {
 
      "version": "0.3.0",
 
      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
 
      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
 
      "dev": true,
 
      "requires": {
 
        "prelude-ls": "~1.1.2",
 
        "type-check": "~0.3.2"
 
      }
 
    },
 
    "license-checker": {
 
      "version": "25.0.1",
 
      "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz",
 
@@ -580,6 +1104,12 @@
 
        "treeify": "^1.1.0"
 
      }
 
    },
 
    "lodash": {
 
      "version": "4.17.15",
 
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
 
      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
 
      "dev": true
 
    },
 
    "mime": {
 
      "version": "1.6.0",
 
      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
 
@@ -604,6 +1134,12 @@
 
        "mime-db": "1.40.0"
 
      }
 
    },
 
    "mimic-fn": {
 
      "version": "2.1.0",
 
      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
 
      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
 
      "dev": true
 
    },
 
    "minimatch": {
 
      "version": "3.0.4",
 
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
 
@@ -634,6 +1170,24 @@
 
      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
 
      "dev": true
 
    },
 
    "mute-stream": {
 
      "version": "0.0.8",
 
      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
 
      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
 
      "dev": true
 
    },
 
    "natural-compare": {
 
      "version": "1.4.0",
 
      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
 
      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
 
      "dev": true
 
    },
 
    "nice-try": {
 
      "version": "1.0.5",
 
      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
 
      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
 
      "dev": true
 
    },
 
    "nopt": {
 
      "version": "4.0.1",
 
      "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
 
@@ -672,6 +1226,29 @@
 
        "wrappy": "1"
 
      }
 
    },
 
    "onetime": {
 
      "version": "5.1.0",
 
      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
 
      "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
 
      "dev": true,
 
      "requires": {
 
        "mimic-fn": "^2.1.0"
 
      }
 
    },
 
    "optionator": {
 
      "version": "0.8.3",
 
      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
 
      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
 
      "dev": true,
 
      "requires": {
 
        "deep-is": "~0.1.3",
 
        "fast-levenshtein": "~2.0.6",
 
        "levn": "~0.3.0",
 
        "prelude-ls": "~1.1.2",
 
        "type-check": "~0.3.2",
 
        "word-wrap": "~1.2.3"
 
      }
 
    },
 
    "os-homedir": {
 
      "version": "1.0.2",
 
      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
 
@@ -694,12 +1271,27 @@
 
        "os-tmpdir": "^1.0.0"
 
      }
 
    },
 
    "parent-module": {
 
      "version": "1.0.1",
 
      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 
      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
 
      "dev": true,
 
      "requires": {
 
        "callsites": "^3.0.0"
 
      }
 
    },
 
    "path-is-absolute": {
 
      "version": "1.0.1",
 
      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
 
      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
 
      "dev": true
 
    },
 
    "path-key": {
 
      "version": "2.0.1",
 
      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
 
      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
 
      "dev": true
 
    },
 
    "path-parse": {
 
      "version": "1.0.6",
 
      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
 
@@ -713,6 +1305,18 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "prelude-ls": {
 
      "version": "1.1.2",
 
      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
 
      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
 
      "dev": true
 
    },
 
    "progress": {
 
      "version": "2.0.3",
 
      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
 
      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
 
      "dev": true
 
    },
 
    "promise": {
 
      "version": "7.3.1",
 
      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
 
@@ -741,8 +1345,7 @@
 
      "version": "2.1.1",
 
      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
 
      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
 
      "dev": true,
 
      "optional": true
 
      "dev": true
 
    },
 
    "qs": {
 
      "version": "6.5.2",
 
@@ -779,6 +1382,17 @@
 
        "slash": "^1.0.0"
 
      }
 
    },
 
    "readable-stream": {
 
      "version": "3.6.0",
 
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
 
      "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
 
      "dev": true,
 
      "requires": {
 
        "inherits": "^2.0.3",
 
        "string_decoder": "^1.1.1",
 
        "util-deprecate": "^1.0.1"
 
      }
 
    },
 
    "readdir-scoped-modules": {
 
      "version": "1.1.0",
 
      "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
 
@@ -791,6 +1405,12 @@
 
        "once": "^1.3.0"
 
      }
 
    },
 
    "regexpp": {
 
      "version": "2.0.1",
 
      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
 
      "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
 
      "dev": true
 
    },
 
    "request": {
 
      "version": "2.88.0",
 
      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
 
@@ -829,19 +1449,60 @@
 
        "path-parse": "^1.0.6"
 
      }
 
    },
 
    "resolve-from": {
 
      "version": "4.0.0",
 
      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
 
      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
 
      "dev": true
 
    },
 
    "restore-cursor": {
 
      "version": "3.1.0",
 
      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
 
      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
 
      "dev": true,
 
      "requires": {
 
        "onetime": "^5.1.0",
 
        "signal-exit": "^3.0.2"
 
      }
 
    },
 
    "rimraf": {
 
      "version": "2.6.3",
 
      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
 
      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
 
      "dev": true,
 
      "requires": {
 
        "glob": "^7.1.3"
 
      }
 
    },
 
    "run-async": {
 
      "version": "2.3.0",
 
      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
 
      "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
 
      "dev": true,
 
      "requires": {
 
        "is-promise": "^2.1.0"
 
      }
 
    },
 
    "rxjs": {
 
      "version": "6.5.4",
 
      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
 
      "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
 
      "dev": true,
 
      "requires": {
 
        "tslib": "^1.9.0"
 
      }
 
    },
 
    "safe-buffer": {
 
      "version": "5.2.0",
 
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
 
      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
 
      "dev": true,
 
      "optional": true
 
      "dev": true
 
    },
 
    "safer-buffer": {
 
      "version": "2.1.2",
 
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
 
      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
 
      "dev": true,
 
      "optional": true
 
      "dev": true
 
    },
 
    "select2": {
 
      "version": "3.5.1",
 
@@ -859,12 +1520,52 @@
 
      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
 
      "dev": true
 
    },
 
    "shebang-command": {
 
      "version": "1.2.0",
 
      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
 
      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
 
      "dev": true,
 
      "requires": {
 
        "shebang-regex": "^1.0.0"
 
      }
 
    },
 
    "shebang-regex": {
 
      "version": "1.0.0",
 
      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
 
      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
 
      "dev": true
 
    },
 
    "signal-exit": {
 
      "version": "3.0.2",
 
      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
 
      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
 
      "dev": true
 
    },
 
    "slash": {
 
      "version": "1.0.0",
 
      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
 
      "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
 
      "dev": true
 
    },
 
    "slice-ansi": {
 
      "version": "2.1.0",
 
      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
 
      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
 
      "dev": true,
 
      "requires": {
 
        "ansi-styles": "^3.2.0",
 
        "astral-regex": "^1.0.0",
 
        "is-fullwidth-code-point": "^2.0.0"
 
      },
 
      "dependencies": {
 
        "is-fullwidth-code-point": {
 
          "version": "2.0.0",
 
          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
 
          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
 
          "dev": true
 
        }
 
      }
 
    },
 
    "slide": {
 
      "version": "1.1.6",
 
      "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
 
@@ -938,6 +1639,12 @@
 
        "spdx-ranges": "^2.0.0"
 
      }
 
    },
 
    "sprintf-js": {
 
      "version": "1.0.3",
 
      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 
      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
 
      "dev": true
 
    },
 
    "sshpk": {
 
      "version": "1.16.1",
 
      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
 
@@ -956,6 +1663,60 @@
 
        "tweetnacl": "~0.14.0"
 
      }
 
    },
 
    "string-width": {
 
      "version": "4.2.0",
 
      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
 
      "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
 
      "dev": true,
 
      "requires": {
 
        "emoji-regex": "^8.0.0",
 
        "is-fullwidth-code-point": "^3.0.0",
 
        "strip-ansi": "^6.0.0"
 
      },
 
      "dependencies": {
 
        "strip-ansi": {
 
          "version": "6.0.0",
 
          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
 
          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
 
          "dev": true,
 
          "requires": {
 
            "ansi-regex": "^5.0.0"
 
          }
 
        }
 
      }
 
    },
 
    "string_decoder": {
 
      "version": "1.3.0",
 
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
 
      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
 
      "dev": true,
 
      "requires": {
 
        "safe-buffer": "~5.2.0"
 
      }
 
    },
 
    "strip-ansi": {
 
      "version": "5.2.0",
 
      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
 
      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
 
      "dev": true,
 
      "requires": {
 
        "ansi-regex": "^4.1.0"
 
      },
 
      "dependencies": {
 
        "ansi-regex": {
 
          "version": "4.1.0",
 
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
 
          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
 
          "dev": true
 
        }
 
      }
 
    },
 
    "strip-json-comments": {
 
      "version": "3.0.1",
 
      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
 
      "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
 
      "dev": true
 
    },
 
    "supports-color": {
 
      "version": "5.5.0",
 
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
 
@@ -965,6 +1726,64 @@
 
        "has-flag": "^3.0.0"
 
      }
 
    },
 
    "table": {
 
      "version": "5.4.6",
 
      "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
 
      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
 
      "dev": true,
 
      "requires": {
 
        "ajv": "^6.10.2",
 
        "lodash": "^4.17.14",
 
        "slice-ansi": "^2.1.0",
 
        "string-width": "^3.0.0"
 
      },
 
      "dependencies": {
 
        "emoji-regex": {
 
          "version": "7.0.3",
 
          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
 
          "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
 
          "dev": true
 
        },
 
        "is-fullwidth-code-point": {
 
          "version": "2.0.0",
 
          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
 
          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
 
          "dev": true
 
        },
 
        "string-width": {
 
          "version": "3.1.0",
 
          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
 
          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
 
          "dev": true,
 
          "requires": {
 
            "emoji-regex": "^7.0.1",
 
            "is-fullwidth-code-point": "^2.0.0",
 
            "strip-ansi": "^5.1.0"
 
          }
 
        }
 
      }
 
    },
 
    "text-table": {
 
      "version": "0.2.0",
 
      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
 
      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
 
      "dev": true
 
    },
 
    "through": {
 
      "version": "2.3.8",
 
      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
 
      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
 
      "dev": true
 
    },
 
    "tmp": {
 
      "version": "0.0.33",
 
      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
 
      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
 
      "dev": true,
 
      "requires": {
 
        "os-tmpdir": "~1.0.2"
 
      }
 
    },
 
    "tough-cookie": {
 
      "version": "2.4.3",
 
      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
 
@@ -991,6 +1810,12 @@
 
      "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==",
 
      "dev": true
 
    },
 
    "tslib": {
 
      "version": "1.11.0",
 
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.0.tgz",
 
      "integrity": "sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg==",
 
      "dev": true
 
    },
 
    "tunnel-agent": {
 
      "version": "0.6.0",
 
      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
 
@@ -1008,16 +1833,36 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "type-check": {
 
      "version": "0.3.2",
 
      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
 
      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
 
      "dev": true,
 
      "requires": {
 
        "prelude-ls": "~1.1.2"
 
      }
 
    },
 
    "type-fest": {
 
      "version": "0.8.1",
 
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
 
      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
 
      "dev": true
 
    },
 
    "uri-js": {
 
      "version": "4.2.2",
 
      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
 
      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
 
      "dev": true,
 
      "optional": true,
 
      "requires": {
 
        "punycode": "^2.1.0"
 
      }
 
    },
 
    "util-deprecate": {
 
      "version": "1.0.2",
 
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 
      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
 
      "dev": true
 
    },
 
    "util-extend": {
 
      "version": "1.0.3",
 
      "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
 
@@ -1031,6 +1876,12 @@
 
      "dev": true,
 
      "optional": true
 
    },
 
    "v8-compile-cache": {
 
      "version": "2.1.0",
 
      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
 
      "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
 
      "dev": true
 
    },
 
    "validate-npm-package-license": {
 
      "version": "3.0.4",
 
      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
 
@@ -1053,11 +1904,35 @@
 
        "extsprintf": "^1.2.0"
 
      }
 
    },
 
    "which": {
 
      "version": "1.3.1",
 
      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
 
      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
 
      "dev": true,
 
      "requires": {
 
        "isexe": "^2.0.0"
 
      }
 
    },
 
    "word-wrap": {
 
      "version": "1.2.3",
 
      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
 
      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
 
      "dev": true
 
    },
 
    "wrappy": {
 
      "version": "1.0.2",
 
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
 
      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
 
      "dev": true
 
    },
 
    "write": {
 
      "version": "1.0.3",
 
      "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
 
      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
 
      "dev": true,
 
      "requires": {
 
        "mkdirp": "^0.5.1"
 
    }
 
  }
 
}
 
}
kallithea/front-end/package.json
Show inline comments
 
@@ -14,6 +14,8 @@
 
    "select2-bootstrap-css": "1.4.6"
 
  },
 
  "devDependencies": {
 
    "eslint": "6.8.0",
 
    "eslint-plugin-html": "6.0.0",
 
    "less": "3.10.3",
 
    "less-plugin-clean-css": "1.5.1",
 
    "license-checker": "25.0.1"
kallithea/front-end/style.less
Show inline comments
 
@@ -937,8 +937,8 @@ div.annotatediv {
 
  background-color: @kallithea-theme-main-color;
 
  border: 0;
 
}
 
#content #context-pages .follow .show-following,
 
#content #context-pages .following .show-follow {
 
#content .follow .show-following,
 
#content .following .show-follow {
 
  display: none;
 
}
 

	
kallithea/i18n/how_to
Show inline comments
 
@@ -55,9 +55,9 @@ the translation files (`*.po`).
 

	
 
First update the translation strings::
 

	
 
    python2 setup.py extract_messages
 
    python3 setup.py extract_messages
 

	
 
Then regenerate the translation files. This could either be done with `python2
 
Then regenerate the translation files. This could either be done with `python3
 
setup.py update_catalog` or with `msgmerge` from the `gettext` package. As
 
Weblate is also touching these translation files, it is preferred to use the
 
same tools (`msgmerge`) and settings as Weblate to minimize the diff::
 
@@ -73,11 +73,11 @@ Manual creation of a new language transl
 
In the prepared development environment, run the following to ensure
 
all translation strings are extracted and up-to-date::
 

	
 
    python2 setup.py extract_messages
 
    python3 setup.py extract_messages
 

	
 
Create new language by executing following command::
 

	
 
    python2 setup.py init_catalog -l <new_language_code>
 
    python3 setup.py init_catalog -l <new_language_code>
 

	
 
This creates a new translation under directory `kallithea/i18n/<new_language_code>`
 
based on the translation template file, `kallithea/i18n/kallithea.pot`.
 
@@ -90,7 +90,7 @@ translation file for errors by executing
 

	
 
Finally, compile the translations::
 

	
 
    python2 setup.py compile_catalog -l <new_language_code>
 
    python3 setup.py compile_catalog -l <new_language_code>
 

	
 

	
 
Manually updating translations
 
@@ -98,11 +98,11 @@ Manually updating translations
 

	
 
Extract the latest versions of strings for translation by running::
 

	
 
    python2 setup.py extract_messages
 
    python3 setup.py extract_messages
 

	
 
Update the PO file by doing::
 

	
 
    python2 setup.py update_catalog -l <new_language_code>
 
    python3 setup.py update_catalog -l <new_language_code>
 

	
 
Edit the newly updated translation file. Repeat all steps after the
 
`init_catalog` step from the 'new translation' instructions above.
kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po
Show inline comments
 
@@ -206,9 +206,6 @@ msgstr "Er is een fout opgetreden tijden
 
msgid "Changeset %s not found"
 
msgstr "Changeset %s werd niet gevonden"
 

	
 
msgid "SSH key %r not found"
 
msgstr "SSH key %r werd niet gevonden"
 

	
 
msgid "Add repos"
 
msgstr "Repositories toevoegen"
 

	
kallithea/i18n/pl/LC_MESSAGES/kallithea.po
Show inline comments
 
@@ -36,6 +36,9 @@ msgstr "Nie można znaleźć innego repozytorium %s"
 
msgid "No response"
 
msgstr "Brak odpowiedzi"
 

	
 
msgid "Unknown error"
 
msgstr "Nieznany błąd"
 

	
 
msgid ""
 
"The request could not be understood by the server due to malformed syntax."
 
msgstr ""
 
@@ -125,6 +128,9 @@ msgstr "Etykiety"
 
msgid "An error occurred during repository forking %s"
 
msgstr "Wystąpił błąd podczas rozgałęzienia %s repozytorium"
 

	
 
msgid "Groups"
 
msgstr "Grupy"
 

	
 
msgid "Repositories"
 
msgstr "Repozytoria"
 

	
 
@@ -155,6 +161,9 @@ msgstr "Twój link zresetowania hasła został wysłany"
 
msgid "Invalid password reset token"
 
msgstr "Nieprawidłowy token resetowania hasła"
 

	
 
msgid "Successfully updated password"
 
msgstr "Pomyślnie zaktualizowano hasło"
 

	
 
msgid "%s (closed)"
 
msgstr "%s (zamknięty)"
 

	
 
@@ -247,6 +256,9 @@ msgstr "Twoje konto zostało pomyślnie zaktualizowane"
 
msgid "Error occurred during update of user %s"
 
msgstr "wystąpił błąd podczas aktualizacji użytkownika %s"
 

	
 
msgid "Error occurred during update of user password"
 
msgstr "Wystąpił błąd w trakcie aktualizacji hasła użytkownika"
 

	
 
msgid "Added email %s to user"
 
msgstr "Dodano e-mail %s do użytkownika"
 

	

Changeset was too big and was cut off... Show full diff anyway

0 comments (0 inline, 0 general)