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
64 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
 
@@ -6,16 +6,16 @@ node {
 
                  strategy: [$class: 'LogRotator',
 
                              artifactDaysToKeepStr: '',
 
                              artifactNumToKeepStr: '10',
 
                              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'
 
    }
 

	
 
    stage('checkout') {
 
        checkout scm
 
        if (isUnix()) {
README.rst
Show inline comments
 
@@ -21,14 +21,14 @@ works on Unix-like systems and Windows.
 
Kallithea was forked from RhodeCode in July 2014 and has been heavily modified.
 

	
 

	
 
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
 

	
 
The development repository is kept very stable and used in production by the
 
developers -- you can do the same.
 

	
 
@@ -170,13 +170,12 @@ Kallithea 0.3.2 and earlier supports mig
 
installation. To migrate, install Kallithea 0.3.2 and follow the
 
instructions in the 0.3.2 README to perform a one-time conversion of the
 
database from RhodeCode to Kallithea, before upgrading to this version
 
of Kallithea.
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _Python: http://www.python.org/
 
.. _Sphinx: http://sphinx.pocoo.org/
 
.. _Mercurial: http://mercurial.selenic.com/
 
.. _Bitbucket: http://bitbucket.org/
 
.. _GitHub: http://github.com/
 
.. _Subversion: http://subversion.tigris.org/
conftest.py
Show inline comments
 
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')):
 
        return True
 

	
 
    # during doctest verification, normally all python files will be imported.
 
@@ -33,6 +42,13 @@ def doctest_mock_ugettext(request):
 
    In a doctest, enable this fixture using:
 
        >>> getfixture('doctest_mock_ugettext')
 
    """
 
    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]
 

	
 
################################################################################
 
## Email settings                                                             ##
 
##                                                                            ##
 
@@ -123,13 +123,13 @@ commit_parse_limit = 25
 
## This value will be written inside the git hook scripts as the text
 
## after '#!' (shebang). When empty or not defined, the value of
 
## 'sys.executable' at the time of installation of the git hooks is
 
## 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
 

	
 
## git rev filter option, --all is the default filter, if you need to
 
## hide all refs in changelog switch this to --branches --tags
 
@@ -195,13 +195,13 @@ issue_server_link = https://issues.examp
 

	
 
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
 
#issue_sub_wiki = WIKI-\1
 

	
 
## alternative return HTTP header for failed authentication. Default HTTP
 
@@ -213,18 +213,18 @@ auth_ret_code =
 
allow_repo_location_change = True
 

	
 
## allows to setup custom hooks in settings page
 
allow_custom_hooks_settings = True
 

	
 
## extra extensions for indexing, space separated and without the leading '.'.
 
# index.extensions =
 
#index.extensions =
 
#    gemfile
 
#    lock
 

	
 
## extra filenames for indexing, space separated
 
# index.filenames =
 
#index.filenames =
 
#    .dockerignore
 
#    .editorconfig
 
#    INSTALL
 
#    CHANGELOG
 

	
 
####################################
 
@@ -247,52 +247,46 @@ ssh_enabled = false
 
#ssh_locale = C.UTF-8
 

	
 
####################################
 
###        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        ####
 
####################################
 

	
 
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        ####
 
####################################
 

	
 
## Name of session cookie. Should be unique for a given host and path, even when running
 
@@ -321,33 +315,39 @@ session.secret = development-not-secret
 

	
 
## Database storage of session data.
 
#session.type = ext:database
 
#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
 
get trace_errors.error_email = email_to
 
get trace_errors.smtp_username = smtp_username
 
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       ###
 
##################################
 

	
 
logview.sqlalchemy = #faa
 
@@ -355,16 +355,16 @@ logview.pylons.templating = #bfb
 
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
 

	
 
################################
 
### ALEMBIC CONFIGURATION   ####
 
################################
 
@@ -389,15 +389,14 @@ keys = generic, color_formatter, color_f
 
## LOGGERS ##
 
#############
 

	
 
[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
 
level = DEBUG
 
handlers =
 
qualname = routes.middleware
 
@@ -434,16 +433,16 @@ handlers =
 
qualname = gearbox
 

	
 
[logger_sqlalchemy]
 
level = WARN
 
handlers =
 
qualname = sqlalchemy.engine
 
# For coloring based on log level and pretty printing of SQL:
 
# level = INFO
 
# handlers = console_color_sql
 
# propagate = 0
 
## For coloring based on log level and pretty printing of SQL:
 
#level = INFO
 
#handlers = console_color_sql
 
#propagate = 0
 

	
 
[logger_whoosh_indexer]
 
#level = WARN
 
level = DEBUG
 
handlers =
 
qualname = whoosh_indexer
 
@@ -465,19 +464,19 @@ qualname = backlash
 
[handler_console]
 
class = StreamHandler
 
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
 

	
 
[handler_null]
 
class = NullHandler
 
@@ -502,19 +501,19 @@ format = %(asctime)s.%(msecs)03d %(level
 
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
 
@@ -132,16 +132,16 @@ Certificate Checks : optional
 
    HARD
 
        The same as DEMAND.
 

	
 
.. _Custom CA Certificates:
 

	
 
Custom CA Certificates : optional
 
    Directory used by OpenSSL to find CAs for validating the LDAP server certificate.
 
    Python 2.7.10 and later default to using the system certificate store, and
 
    this should thus not be necessary when using certificates signed by a CA
 
    trusted by the system.
 
    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.
 

	
 
.. _Base DN:
 

	
 
Base DN : required
docs/api/models.rst
Show inline comments
 
@@ -10,15 +10,12 @@ The :mod:`models` module
 
.. automodule:: kallithea.model.comment
 
   :members:
 

	
 
.. automodule:: kallithea.model.permission
 
   :members:
 

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

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

	
 
.. automodule:: kallithea.model.repo_group
 
   :members:
 

	
docs/conf.py
Show inline comments
 
@@ -43,14 +43,14 @@ source_suffix = '.rst'
 
#source_encoding = 'utf-8-sig'
 

	
 
# The master toctree document.
 
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
 
# built documents.
 
#
 
# The short X.Y version.
 
@@ -184,14 +184,14 @@ htmlhelp_basename = 'Kallithea-docs'
 
# The font size ('10pt', '11pt' or '12pt').
 
#latex_font_size = '10pt'
 

	
 
# 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
 
# the title page.
 
#latex_logo = None
 

	
 
@@ -217,13 +217,13 @@ latex_documents = [
 

	
 
# -- Options for manual page output --------------------------------------------
 

	
 
# 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)
 
]
 

	
 

	
 
# Example configuration for intersphinx: refer to the Python standard library.
 
intersphinx_mapping = {'http://docs.python.org/': None}
docs/contributing.rst
Show inline comments
 
@@ -29,13 +29,13 @@ Getting started
 
---------------
 

	
 
To get started with Kallithea development::
 

	
 
        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
 
        kallithea-cli config-create my.ini
 
        kallithea-cli db-create -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp
 
        kallithea-cli front-end-build
 
@@ -89,14 +89,13 @@ by invoking ``py.test`` from the project
 

	
 
Note that on unix systems, the temporary directory (``/tmp`` or where
 
``$TMPDIR`` points) must allow executable files; Git hooks must be executable,
 
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
 
there.
 

	
 
It is possible to avoid recreating the full test database on each invocation of
 
@@ -196,14 +195,13 @@ Coding guidelines
 

	
 
We don't have a formal coding/formatting standard. We are currently using a mix
 
of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and
 
consistency with existing code. Run ``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.
 

	
 
We primarily support Linux and OS X on the server side but Windows should also work.
 

	
 
@@ -235,14 +233,14 @@ Notes on the SQLAlchemy session
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Each HTTP request runs inside an independent SQLAlchemy session (as well
 
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
 
that SQLAlchemy will ensure that they're kept in sync with the database
 
(but also means that they cannot be shared across requests).
 

	
 
@@ -265,12 +263,26 @@ rarely needed:
 
SQLAlchemy also flushes changes to the database automatically; manually
 
calling ``Session().flush`` is usually only necessary when the Python
 
code needs the database to assign an "auto-increment" primary key ID to
 
a freshly created model object (before flushing, the ID attribute will
 
be ``None``).
 

	
 
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
 
^^^^^^^^^^^^^^^^^^^^
 

	
 
It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed
 
over the Kallithea web interface, allowing you to see:
 

	
docs/index.rst
Show inline comments
 
@@ -75,13 +75,12 @@ Developer guide
 

	
 
   contributing
 
   dev/translation
 
   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/
 
.. _bitbucket: http://bitbucket.org/
 
.. _subversion: http://subversion.tigris.org/
 
.. _git: http://git-scm.com/
docs/installation.rst
Show inline comments
 
@@ -32,56 +32,56 @@ Kallithea dependencies requires a workin
 
headers. Depending on your configuration, you may also need to install
 
Git and development packages for the database of your choice.
 

	
 
For Debian and Ubuntu, the following command will ensure that a reasonable
 
set of dependencies is installed::
 

	
 
    sudo apt-get install build-essential git python-pip python-virtualenv libffi-dev python-dev
 
    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:
 

	
 

	
 
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`.
 

	
 
.. _installation-virtualenv:
 

	
 

	
 
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
 
    pip install --upgrade pip setuptools
 

	
 
.. note:: You can't use UNIX ``sudo`` to source the ``virtualenv`` script; it
 
@@ -130,9 +130,6 @@ globally without using the system's pack
 

	
 
To install as a regular user in ``~/.local``, you can use::
 

	
 
    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)
 
=====================================================================
 

	
 
The following is documented using IIS 7/8 terminology. There should be nothing
 
preventing you from applying this on IIS 6 well.
 
@@ -63,13 +65,13 @@ The ISAPI handler can be generated using
 

	
 
This will generate a ``dispatch.py`` file in the current directory that contains
 
the necessary components to finalize an installation into IIS. Once this file
 
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
 
``--virtualdir`` specified above pointing to ``_dispatch.dll``.
 

	
 
The ISAPI handler is registered to all file extensions, so it will automatically
 
@@ -116,13 +118,13 @@ different options for finding issues exi
 
is great at finding issues until they exist inside Kallithea, at which point the
 
ISAPI-WSGI wrapper above uses ``win32traceutil``, which is part of ``pywin32``.
 

	
 
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
 
traces, making it a lot easier to identify issues.
 

	
 

	
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)
 
====================================================
 

	
 

	
 
First time install
 
@@ -14,24 +16,22 @@ Tested on Windows 8.1, Windows Server 20
 

	
 
To install on an older version of Windows, see `<installation_win_old.html>`_
 

	
 
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
 
^^^^^^^^^^^^^^^^^^^^
 

	
 
Add Python BIN folder to the path. This can be done manually (editing
 
"PATH" environment variable) or by using Windows Support Tools that
 
@@ -39,54 +39,30 @@ come pre-installed in Windows Vista/7 an
 

	
 
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
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Download pywin32 from:
 
http://sourceforge.net/projects/pywin32/files/
 

	
 
- 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
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Create a Kallithea folder structure.
 

	
 
This is only an example to install Kallithea. Of course, you can
 
@@ -105,30 +81,24 @@ Step 6 -- Install virtualenv
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
.. note::
 
   A python virtual environment will allow for isolation between the Python packages of your system and those used for Kallithea.
 
   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.
 

	
 
In a command prompt type (adapting paths if necessary)::
 

	
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)
 
==========================================================
 

	
 

	
 
First-time install
 
@@ -57,43 +59,40 @@ choose "Visual C++ 2008 Express" when in
 
   I am not sure why this is not necessary for 32-bit.
 
   Copy C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat to C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64\vcvarsamd64.bat
 

	
 
Step 2 -- Install Python
 
^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Install Python 2.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::
 

	
 
   64-bit: Just download and install the 64-bit version of python.
 

	
 
Step 3 -- Install Win32py extensions
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Download pywin32 from:
 
http://sourceforge.net/projects/pywin32/files/
 

	
 
- 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
 
^^^^^^^^^^^^^^^^^^^^
 

	
 
Add Python BIN folder to the path
 

	
 
@@ -114,13 +113,13 @@ that came preinstalled in Vista/7 and ca
 

	
 
  Open a CMD and type::
 

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

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

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

	
 
Create a Kallithea folder structure
 

	
 
@@ -136,28 +135,16 @@ Create the following folder structure::
 
  C:\Kallithea\Env
 
  C:\Kallithea\Repos
 

	
 
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
 
^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

	
 
Finally, install Kallithea
 

	
docs/overview.rst
Show inline comments
 
@@ -9,13 +9,13 @@ installing Kallithea.
 

	
 

	
 
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
 
corresponds to a ``site-packages`` directory somewhere where packages can be
 
installed.
 

	
 
@@ -27,13 +27,13 @@ environment used for running Kallithea.
 
  that would require running pip_ as root and it would be hard to uninstall or
 
  upgrade and is probably not a good idea unless using a package manager.
 

	
 
- 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.
 

	
 
We recommend using virtualenv for installing Kallithea.
 

	
 

	
 
@@ -95,13 +95,13 @@ installed.
 

	
 
- Kallithea can also be installed from ready-made packages using a package manager.
 
  The official released versions are available on PyPI_ and can be downloaded and
 
  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
 
  but build the Kallithea package itself locally instead of downloading it.)
 

	
 
.. note::
 
@@ -162,13 +162,12 @@ also often used inside organizations wit
 
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/
 
.. _mod_wsgi: https://code.google.com/p/modwsgi/
 
.. _isapi-wsgi: https://github.com/hexdump42/isapi-wsgi
 
.. _uWSGI: https://uwsgi-docs.readthedocs.org/en/latest/
docs/setup.rst
Show inline comments
 
@@ -329,17 +329,17 @@ or Redis_.
 

	
 
The use of Celery is configured in the Kallithea ini configuration file.
 
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::
 

	
 
  kallithea-cli celery-run -c my.ini
 

	
 
Extra options to the Celery worker can be passed after ``--`` - see ``-- -h``
 
@@ -554,13 +554,13 @@ that, you'll need to:
 
      os.environ['PYTHON_EGG_CACHE'] = '/srv/kallithea/.egg-cache'
 

	
 
      # sometimes it's needed to set the current dir
 
      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
 
      fileConfig(ini, {'__file__': ini, 'here': '/srv/kallithea'})
 
      from paste.deploy import loadapp
 
      application = loadapp('config:' + ini)
 
@@ -621,13 +621,12 @@ Other configuration files
 
A number of `example init.d scripts`__ can be found in
 
the ``init.d`` directory of the Kallithea source.
 

	
 
.. __: 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/
 
.. _Celery: http://celeryproject.org/
 
.. _Celery documentation: http://docs.celeryproject.org/en/latest/getting-started/index.html
 
.. _RabbitMQ: http://www.rabbitmq.com/
docs/upgrade.rst
Show inline comments
 
@@ -238,9 +238,6 @@ To update the hooks of your Git reposito
 
* Select the checkbox *Install Git hooks*
 
* Click the button *Rescan repositories*
 

	
 
.. 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
 
@@ -5,13 +5,13 @@ Troubleshooting
 
===============
 

	
 
:Q: **Missing static files?**
 
:A: Make sure either to set the ``static_files = true`` in the .ini file or
 
   double check the root path for your http setup. It should point to
 
   for example:
 
   ``/home/my-virtual-python/lib/python2.7/site-packages/kallithea/public``
 
   ``/home/my-virtual-python/lib/python3.7/site-packages/kallithea/public``
 

	
 
|
 

	
 
:Q: **Can't install celery/rabbitmq?**
 
:A: Don't worry. Kallithea works without them, too. No extra setup is required.
 
    Try out the great Celery docs for further help.
 
@@ -64,12 +64,11 @@ Troubleshooting
 

	
 
:Q: **Requests hanging on Windows**
 
:A: Please try out with disabled Antivirus software, there are some known problems with Eset Antivirus. Make sure
 
    you have installed the latest Windows patches (especially KB2789397).
 

	
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _python: http://www.python.org/
 
.. _mercurial: https://www.mercurial-scm.org/
 
.. _celery: http://celeryproject.org/
 
.. _rabbitmq: http://www.rabbitmq.com/
 
.. _python-ldap: http://www.python-ldap.org/
kallithea/__init__.py
Show inline comments
 
@@ -28,19 +28,22 @@ Original author and date, and relevant c
 
"""
 

	
 
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 = {}
 

	
 
# Linked module for extensions
 
EXTENSIONS = {}
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
 
@@ -26,15 +26,16 @@ Original author and date, and relevant c
 
"""
 

	
 
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'
 
FORMAT_PRETTY = 'pretty'
 
FORMAT_JSON = 'json'
 

	
 
@@ -64,18 +65,18 @@ def api_call(apikey, apihost, method=Non
 
        }
 

	
 
    if not method:
 
        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
 

	
 
    else:
 
        _formatted_json = pprint.pformat(json_data)
 
@@ -104,13 +105,13 @@ class RcConf(object):
 
        if autoload:
 
            self._conf = self.load_config()
 

	
 
    def __getitem__(self, key):
 
        return self._conf[key]
 

	
 
    def __nonzero__(self):
 
    def __bool__(self):
 
        if self._conf:
 
            return True
 
        return False
 

	
 
    def __eq__(self, other):
 
        return self._conf.__eq__(other)
 
@@ -125,13 +126,13 @@ class RcConf(object):
 
        :param config:
 
        """
 
        update = False
 
        if os.path.exists(self._conf_name):
 
            update = True
 
        with open(self._conf_name, 'wb') as f:
 
            json.dump(config, f, indent=4)
 
            ext_json.dump(config, f, indent=4)
 
            f.write('\n')
 

	
 
        if update:
 
            sys.stdout.write('Updated config in %s\n' % self._conf_name)
 
        else:
 
            sys.stdout.write('Created new config in %s\n' % self._conf_name)
 
@@ -143,23 +144,23 @@ class RcConf(object):
 

	
 
        :param new_config:
 
        """
 
        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')
 

	
 
        config.update(new_config)
 
        self.make_config(config)
 

	
 
    def load_config(self):
 
        """
 
        Loads config from file and returns loaded JSON object
 
        """
 
        try:
 
            with open(self._conf_name, 'rb') as conf:
 
                return json.load(conf)
 
                return ext_json.load(conf)
 
        except IOError as e:
 
            #sys.stderr.write(str(e) + '\n')
 
            pass
kallithea/bin/kallithea_api.py
Show inline comments
 
@@ -22,18 +22,17 @@ Original author and date, and relevant c
 
:created_on: Jun 3, 2012
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
: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):
 
    usage = (
 
      "kallithea-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
 
      "[--config=CONFIG] [--save-config] "
 
@@ -57,13 +56,13 @@ def argparser(argv):
 
    )
 
    group.add_argument('--format', dest='format', type=str,
 
            help='output format default: `%s` can '
 
                 '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
 

	
 

	
 
def main(argv=None):
 
    """
 
    Main execution function for cli
 
@@ -98,13 +97,13 @@ def main(argv=None):
 

	
 
    # if we don't have method here it's an error
 
    if not method:
 
        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()
 
    if args.format == FORMAT_PRETTY:
 
        print('Calling method %s => %s' % (method, apihost))
 

	
kallithea/bin/kallithea_cli.py
Show inline comments
 
@@ -22,6 +22,11 @@ import kallithea.bin.kallithea_cli_iis
 
import kallithea.bin.kallithea_cli_index
 
import kallithea.bin.kallithea_cli_ishell
 
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
 
@@ -9,23 +9,24 @@
 
# 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/>.
 

	
 
import cStringIO
 
import configparser
 
import functools
 
import logging.config
 
import os
 
import re
 
import sys
 

	
 
import click
 
import paste.deploy
 

	
 
import kallithea
 
import kallithea.config.middleware
 

	
 

	
 
# kallithea_cli is usually invoked through the 'kallithea-cli' wrapper script
 
# that is installed by setuptools, as specified in setup.py console_scripts
 
# entry_points. The script will be using the right virtualenv (if any), and for
 
# Unix, it will contain #! pointing at the right python executable. The script
 
@@ -68,16 +69,16 @@ def register_command(config_file=False, 
 
            @click.option('--config_file', '-c', help="Path to .ini file with app configuration.",
 
                type=click.Path(dir_okay=False, exists=True, readable=True), required=True)
 
            @functools.wraps(annotated) # reuse meta data from the wrapped function so click can see other options
 
            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
 
    return cli_command
kallithea/bin/kallithea_cli_celery.py
Show inline comments
 
@@ -9,12 +9,13 @@
 
# 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/>.
 

	
 
import celery.bin.worker
 
import click
 

	
 
import kallithea
 
import kallithea.bin.kallithea_cli_base as cli_base
 

	
 

	
 
@@ -28,13 +29,12 @@ def celery_run(celery_args):
 

	
 
    Any extra arguments you pass to this command will be passed through to
 
    Celery. Use '--' before such extra arguments to avoid options to be parsed
 
    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
 
@@ -86,12 +86,22 @@ def config_create(config_file, key_value
 
            raise ValueError("Invalid name=value parameter %r" % parameter)
 

	
 
    # use default that cannot be replaced
 
    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)
 
        click.echo('Wrote new config file in %s' % config_file_abs)
 
        click.echo("Don't forget to build the front-end using 'kallithea-cli front-end-build'.")
 

	
kallithea/bin/kallithea_cli_db.py
Show inline comments
 
@@ -64,13 +64,13 @@ def db_create(user, password, email, rep
 
    dbmanage.admin_prompt()
 
    dbmanage.create_permissions()
 
    dbmanage.populate_default_permissions()
 
    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:
 
        click.echo('Initial repository scan: added following repositories:')
 
        click.echo('\t%s' % '\n\t'.join(added))
 
    else:
kallithea/bin/kallithea_cli_iis.py
Show inline comments
 
@@ -64,12 +64,13 @@ def iis_install(virtualdir):
 
    """Install into IIS using isapi-wsgi."""
 

	
 
    config_file_abs = kallithea.CONFIG['__file__']
 

	
 
    try:
 
        import isapi_wsgi
 
        assert isapi_wsgi
 
    except ImportError:
 
        sys.stderr.write('missing requirement: isapi-wsgi not installed\n')
 
        sys.exit(1)
 

	
 
    dispatchfile = os.path.join(os.getcwd(), 'dispatch.py')
 
    click.echo('Writing %s' % dispatchfile)
kallithea/bin/kallithea_cli_index.py
Show inline comments
 
@@ -33,13 +33,13 @@ from kallithea.model.repo import RepoMod
 

	
 

	
 
@cli_base.register_command(config_file_initialize_app=True)
 
@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"""
 

	
 
    index_location = kallithea.CONFIG['index_dir']
 
    load_rcextensions(kallithea.CONFIG['here'])
 

	
kallithea/bin/kallithea_cli_ishell.py
Show inline comments
 
@@ -17,18 +17,16 @@ Original author and date, and relevant c
 
:created_on: Apr 4, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
: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)
 
def ishell():
 
    """Interactive shell for Kallithea."""
 
    try:
kallithea/bin/kallithea_cli_repo.py
Show inline comments
 
@@ -23,16 +23,17 @@ import datetime
 
import os
 
import re
 
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
 

	
 

	
 
@cli_base.register_command(config_file_initialize_app=True)
 
@click.option('--remove-missing', is_flag=True,
 
@@ -71,13 +72,13 @@ def repo_update_metadata(repositories):
 
    If no repositories are specified, the caches of all repositories are
 
    updated.
 
    """
 
    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)))
 

	
 
    for repo in repo_list:
 
        # update latest revision metadata in database
 
        repo.update_changeset_cache()
 
@@ -107,13 +108,13 @@ def repo_purge_deleted(ask, older_than):
 
        regex = re.compile(r'((?P<days>\d+?)d)?((?P<hours>\d+?)h)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')
 
        parts = regex.match(val)
 
        if not parts:
 
            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)
 

	
 
    def _extract_date(name):
 
        """
 
@@ -122,15 +123,15 @@ def repo_purge_deleted(ask, older_than):
 

	
 
        :param name:
 
        """
 
        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
 
            '.git' in alldirs or
 
            '.svn' in alldirs or
 
            'objects' in alldirs and ('refs' in alldirs or 'packed-refs' in f)
 
@@ -172,15 +173,14 @@ def repo_purge_deleted(ask, older_than):
 
        click.echo('Considering %s deleted repositories.' % len(to_remove))
 

	
 
    if not ask:
 
        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:
 
            click.echo('Purging repository %s' % path)
 
            shutil.rmtree(path)
 
    else:
kallithea/bin/kallithea_cli_ssh.py
Show inline comments
 
@@ -11,13 +11,12 @@
 
#
 
# 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 logging
 
import os
 
import re
 
import shlex
 
import sys
 

	
 
import click
 

	
 
import kallithea
kallithea/bin/kallithea_gist.py
Show inline comments
 
@@ -22,21 +22,20 @@ Original author and date, and relevant c
 
:created_on: May 9, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
: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):
 
    usage = (
 
      "kallithea-gist [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
 
      "[--config=CONFIG] [--save-config] [GIST OPTIONS] "
 
@@ -66,13 +65,13 @@ def argparser(argv):
 
                       help='gist lifetime in minutes, -1 (DEFAULT) is forever')
 
    group.add_argument('--format', dest='format', type=str,
 
                       help='output format DEFAULT: `%s` can '
 
                       '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
 

	
 

	
 
def _run(argv):
 
    conf = None
 
    parser, args, other = argparser(argv)
kallithea/bin/ldap_sync.py
Show inline comments
 
@@ -22,21 +22,20 @@ Original author and date, and relevant c
 
:created_on: Mar 06, 2013
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
: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()
 
config.read('ldap_sync.conf')
 

	
 

	
 
@@ -77,18 +76,18 @@ class API(object):
 
        This will generate the UUID for validation check after the
 
        response is returned. Handle errors and get the result back.
 
        """
 
        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.")
 

	
 
        if response["error"] is not None:
 
            raise ResponseError(response["error"])
kallithea/config/app_cfg.py
Show inline comments
 
@@ -25,26 +25,27 @@ import sys
 
import alembic.config
 
import mercurial
 
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__)
 

	
 

	
 
class KallitheaAppConfig(AppConfig):
 
@@ -95,31 +96,27 @@ class KallitheaAppConfig(AppConfig):
 
        # in light of possible future framework changes.
 
        self['errorpage.status_codes'] = [400, 401, 403, 404]
 

	
 
        # 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'):
 
            self['i18n.lang'] = 'en'
 
        # 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
 
# 'debug = true' (not in production!)
 
# See the Kallithea documentation for more information.
 
try:
 
    from tgext.debugbar import enable_debugbar
 
    import kajiki # only to check its existence
 
    assert kajiki
 
except ImportError:
 
    pass
 
else:
 
    base_config['renderers'].append('kajiki')
 
    enable_debugbar(base_config)
 

	
 
@@ -158,21 +155,20 @@ def setup_configuration(app):
 
                      'Expected database version id(s): %s\n'
 
                      'If you are a developer and you know what you are doing, you can add `ignore_alembic_revision = True` '
 
                      'to your .ini file to skip the check.\n' % (' '.join(current_heads), ' '.join(available_heads)))
 
            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', '*')
 
    if instance_id == '*':
 
        instance_id = '%s-%s' % (platform.uname()[1], os.getpid())
 
        kallithea.CONFIG['instance_id'] = instance_id
 
@@ -185,14 +181,16 @@ def setup_configuration(app):
 
    # kallithea.CONFIG).
 
    set_vcs_config(kallithea.CONFIG)
 
    set_indexer_config(kallithea.CONFIG)
 

	
 
    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):
 
    config = app.config
 

	
 
    # we want our low level middleware to get to the request ASAP. We don't
 
@@ -210,7 +208,7 @@ def setup_application(app):
 
    if str2bool(config.get('use_wsgi_wrapper')):
 
        app = RequestWrapper(app, config)
 

	
 
    return app
 

	
 

	
 
hooks.register('before_config', setup_application)
 
tg.hooks.register('before_config', setup_application)
kallithea/config/conf.py
Show inline comments
 
@@ -32,13 +32,13 @@ from kallithea.lib import pygmentsutils
 
# extensions will index it's content
 
LANGUAGES_EXTENSIONS_MAP = pygmentsutils.get_extension_descriptions()
 

	
 
# 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()
 

	
 
# list of readme files to search in file tree and display in summary
 
# attached weights defines the search  order lower is first
kallithea/config/middleware.py
Show inline comments
 
@@ -21,17 +21,12 @@ __all__ = ['make_app']
 

	
 
# Use base_config to setup the necessary PasteDeploy application factory.
 
# make_base_app will wrap the TurboGears2 app with all the middleware it needs.
 
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
 
    file used.
 

	
 
    :param global_conf: The global settings for Kallithea (those
 
@@ -44,7 +39,9 @@ def make_app(global_conf, full_stack=Tru
 

	
 
    This is the PasteDeploy factory for the Kallithea application.
 

	
 
    ``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
 
@@ -16,20 +16,40 @@ Routes configuration
 

	
 
The more specific and detailed routes should be defined first so they
 
may take precedent over the more generic routes. For more information
 
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'],
 
                  always_scan=config['debug'])
 
    rmap.minimization = False
 
    rmap.explicit = False
 
@@ -83,13 +103,13 @@ def make_map(config):
 

	
 
    #==========================================================================
 
    # CUSTOM ROUTES HERE
 
    #==========================================================================
 

	
 
    # 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',
 
                 action='repo_switcher_data')
 
    rmap.connect('users_and_groups_data', '/_users_and_groups', controller='home',
 
                 action='users_and_groups_data')
 
@@ -103,13 +123,13 @@ def make_map(config):
 
    # ADMIN REPOSITORY ROUTES
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='admin/repos') as m:
 
        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:.*?}",
 
                  action="update", conditions=dict(method=["POST"],
 
                  function=check_repo))
 
        m.connect("delete_repo", "/repos/{repo_name:.*?}/delete",
 
@@ -118,13 +138,13 @@ def make_map(config):
 
    # ADMIN REPOSITORY GROUPS ROUTES
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='admin/repo_groups') as m:
 
        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:.*?}",
 
                  action="update", conditions=dict(method=["POST"],
 
                                                   function=check_group))
 

	
 
@@ -158,15 +178,15 @@ def make_map(config):
 
    # ADMIN USER ROUTES
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='admin/users') as m:
 
        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}",
 
                  action="update", conditions=dict(method=["POST"]))
 
        m.connect("delete_user", "/users/{id}/delete",
 
                  action="delete", conditions=dict(method=["POST"]))
 
@@ -213,13 +233,13 @@ def make_map(config):
 
    # ADMIN USER GROUPS REST ROUTES
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='admin/user_groups') as m:
 
        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}",
 
                  action="update", conditions=dict(method=["POST"]))
 
        m.connect("delete_users_group", "/user_groups/{id}/delete",
 
                  action="delete", conditions=dict(method=["POST"]))
 
@@ -260,14 +280,13 @@ def make_map(config):
 
        m.connect("admin_permissions_perms", "/permissions/perms",
 
                  action="permission_perms", conditions=dict(method=["GET"]))
 

	
 
    # 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"]))
 

	
 
    # ADMIN AUTH SETTINGS
 
    rmap.connect('auth_settings', '%s/auth' % ADMIN_PREFIX,
 
                 controller='admin/auth_settings', action='auth_settings',
 
@@ -367,13 +386,13 @@ def make_map(config):
 
    # ADMIN GIST
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX,
 
                        controller='admin/gists') as m:
 
        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"]))
 

	
 
        m.connect("gist_delete", "/gists/{gist_id}/delete",
 
                  action="delete", conditions=dict(method=["POST"]))
 
        m.connect("edit_gist", "/gists/{gist_id}/edit",
 
@@ -393,25 +412,25 @@ def make_map(config):
 
                  revision='tip',
 
                  action="show", conditions=dict(method=["GET"]))
 

	
 
    # 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')
 
    #==========================================================================
 
    # API V2
 
    #==========================================================================
 
    with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='api/api',
 
                        action='_dispatch') as m:
 
        m.connect('api', '/api')
 

	
 
    # 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,
 
                 controller='journal', action='journal_atom')
 

	
 
    rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
 
@@ -472,13 +491,13 @@ def make_map(config):
 

	
 
    #==========================================================================
 
    # REPOSITORY ROUTES
 
    #==========================================================================
 
    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:.*?}',
 
                controller='summary',
 
                conditions=dict(function=check_repo))
 

	
 
@@ -541,19 +560,12 @@ def make_map(config):
 
                 conditions=dict(method=["POST"], function=check_repo))
 

	
 
    rmap.connect("edit_repo_advanced_fork", "/{repo_name:.*?}/settings/advanced/fork",
 
                 controller='admin/repos', action="edit_advanced_fork",
 
                 conditions=dict(method=["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))
 
    rmap.connect("edit_repo_remote_update", "/{repo_name:.*?}/settings/remote",
 
                 controller='admin/repos', action="edit_remote",
 
                 conditions=dict(method=["POST"], function=check_repo))
 
@@ -599,13 +611,13 @@ def make_map(config):
 

	
 
    rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
 
                 controller='changeset', action='changeset_info')
 

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

	
 
    rmap.connect('compare_url',
 
                 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref_name:.*?}...{other_ref_type}@{other_ref_name:.*?}',
 
                 controller='compare', action='compare',
 
                 conditions=dict(function=check_repo),
 
@@ -613,13 +625,13 @@ def make_map(config):
 
                            org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
 
                            other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
 
                 )
 

	
 
    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',
 
                 '/{repo_name:.*?}/pull-request-repo-info',
 
                 controller='pullrequests', action='repo_info',
 
                 conditions=dict(function=check_repo, method=["GET"]))
 
@@ -671,13 +683,13 @@ def make_map(config):
 
                controller='summary', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
 
                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}',
 
                controller='changelog', action='changelog_details',
 
                conditions=dict(function=check_repo))
 

	
 
@@ -716,14 +728,14 @@ def make_map(config):
 
                 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
 
                 controller='files', action='raw', revision='tip', f_path='',
 
                 conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_annotate_home',
 
                 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
 
                 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:.*}',
 
                 controller='files', action='edit', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 

	
kallithea/controllers/admin/admin.py
Show inline comments
 
@@ -33,13 +33,12 @@ from sqlalchemy.sql.expression import an
 
from tg import request
 
from tg import tmpl_context as c
 
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
 
from kallithea.lib.page import Page
 
from kallithea.lib.utils2 import remove_prefix, remove_suffix, safe_int
 
from kallithea.model.db import UserLog
 
@@ -58,13 +57,13 @@ def _journal_filter(user_log, search_ter
 
    """
 
    log.debug('Initial search term: %r', search_term)
 
    qry = None
 
    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):
 
        if wc_term.startswith('*') and not wc_term.endswith('*'):
 
            # postfix == endswith
 
            wc_term = remove_prefix(wc_term, prefix='*')
 
@@ -136,15 +135,13 @@ class AdminController(BaseController):
 
        users_log = _journal_filter(users_log, c.search_term)
 

	
 
        users_log = users_log.order_by(UserLog.action_date.desc())
 

	
 
        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')
 

	
 
        return render('admin/admin.html')
kallithea/controllers/admin/auth_settings.py
Show inline comments
 
@@ -34,13 +34,12 @@ from webob.exc import HTTPFound
 

	
 
from kallithea.config.routing import url
 
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
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -84,13 +83,13 @@ class AuthSettingsController(BaseControl
 
        if defaults:
 
            c.defaults.update(defaults)
 

	
 
        # 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,
 
            errors=errors,
 
            prefix_error=False,
 
            encoding="UTF-8",
 
@@ -100,13 +99,13 @@ class AuthSettingsController(BaseControl
 
        self.__load_defaults()
 
        return self.__render(defaults=None, errors=None)
 

	
 
    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']
 
        try:
 
            new_enabled_plugins = _auth_plugins_validator.to_python(request.POST.get('auth_plugins'))
 
        except formencode.Invalid:
kallithea/controllers/admin/defaults.py
Show inline comments
 
@@ -28,13 +28,12 @@ Original author and date, and relevant c
 
import logging
 
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
 

	
 
from kallithea.config.routing import url
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 
@@ -66,13 +65,13 @@ class DefaultsController(BaseController)
 

	
 
    def update(self, id):
 
        _form = DefaultsForm()()
 

	
 
        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'),
 
                    category='success')
 

	
 
        except formencode.Invalid as errors:
kallithea/controllers/admin/gists.py
Show inline comments
 
@@ -37,13 +37,13 @@ from webob.exc import HTTPForbidden, HTT
 

	
 
from kallithea.config.routing import url
 
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
 
from kallithea.model.gist import GistModel
 
from kallithea.model.meta import Session
 

	
 
@@ -68,12 +68,17 @@ class GistsController(BaseController):
 

	
 
    @LoginRequired(allow_default_user=True)
 
    def index(self):
 
        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) \
 
            .order_by(Gist.created_on.desc())
 

	
 
        # MY private
 
@@ -94,13 +99,14 @@ class GistsController(BaseController):
 
        # default show ALL public gists
 
        if not c.show_public and not c.show_private:
 
            gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
 

	
 
        c.gists = gists
 
        p = safe_int(request.GET.get('page'), 1)
 
        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()
 
    def create(self):
 
        self.__load_defaults()
 
        gist_form = GistForm([x[0] for x in c.lifetime_values])()
 
@@ -173,13 +179,16 @@ class GistsController(BaseController):
 
            c.file_changeset, c.files = GistModel().get_gist_files(gist_id,
 
                                                            revision=revision)
 
        except VCSError:
 
            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')
 

	
 
    @LoginRequired()
 
    def edit(self, gist_id, format='html'):
kallithea/controllers/admin/my_account.py
Show inline comments
 
@@ -276,21 +276,21 @@ class MyAccountController(BaseController
 
        try:
 
            new_ssh_key = SshKeyModel().create(request.authuser.user_id,
 
                                               description, public_key)
 
            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
 
    def my_account_ssh_keys_delete(self):
 
        fingerprint = request.POST.get('del_public_key_fingerprint')
 
        try:
 
            SshKeyModel().delete(fingerprint, request.authuser.user_id)
 
            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
 
@@ -22,25 +22,23 @@ Original author and date, and relevant c
 
:created_on: Mar 23, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import itertools
 
import logging
 
import traceback
 

	
 
import formencode
 
from formencode import htmlfill
 
from tg import app_globals, request
 
from tg import tmpl_context as c
 
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
 
from kallithea.lib.base import BaseController, render
 
from kallithea.lib.utils2 import safe_int
 
from kallithea.model.db import RepoGroup, Repository
 
@@ -90,42 +88,36 @@ class RepoGroupsController(BaseControlle
 
            data.update({'g_perm_%s' % p.users_group.users_group_name:
 
                             p.permission.permission_name})
 

	
 
        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
 

	
 
    def index(self, format='html'):
 
        _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,
 
                    ungettext=ungettext)
 
        )
 
        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,
 
                "group_name": repo_group_name(repo_gr.group_name, children_groups),
 
                "desc": h.escape(repo_gr.group_description),
 
                "repos": repo_count,
kallithea/controllers/admin/repos.py
Show inline comments
 
@@ -25,28 +25,30 @@ Original author and date, and relevant c
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import logging
 
import traceback
 

	
 
import celery.result
 
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 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
 
from kallithea.lib.base import BaseRepoController, jsonify, render
 
from kallithea.lib.exceptions import AttachedForksError
 
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
 
from kallithea.model.scm import AvailableRepoGroupChoices, RepoList, ScmModel
 

	
 

	
 
@@ -107,33 +109,32 @@ class ReposController(BaseRepoController
 

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

	
 
    @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(
 
                render('admin/repos/repo_add.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                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')
 
                   % form_result.get('repo_name'))
 
            h.flash(msg, category='error')
 
            raise HTTPFound(location=url('home'))
 
@@ -178,18 +179,16 @@ class ReposController(BaseRepoController
 
    @jsonify
 
    def repo_check(self, repo_name):
 
        c.repo = repo_name
 
        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:
 
            if repo.clone_uri:
 
                h.flash(_('Created repository %s from %s')
 
                        % (repo.repo_name, repo.clone_uri_hidden), category='success')
 
@@ -403,13 +402,13 @@ class ReposController(BaseRepoController
 
            h.flash(msg, category='error')
 
        raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name))
 

	
 
    @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()
 

	
 
        _repos = Repository.query(sorted=True).all()
 
        read_access_repos = RepoList(_repos, perm_level='read')
 
@@ -440,13 +439,13 @@ class ReposController(BaseRepoController
 

	
 
        :param repo_name:
 
        """
 

	
 
        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')
 
            Session().commit()
 
        except Exception:
 
            h.flash(_('An error occurred during setting this'
 
@@ -468,39 +467,21 @@ class ReposController(BaseRepoController
 
            fork = repo.fork.repo_name if repo.fork else _('Nothing')
 
            Session().commit()
 
            h.flash(_('Marked repository %s as fork of %s') % (repo_name, fork),
 
                    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'),
 
                    category='error')
 

	
 
        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'
 
        if request.POST:
 
            try:
 
                ScmModel().pull_changes(repo_name, request.authuser.username, request.ip_addr)
kallithea/controllers/admin/settings.py
Show inline comments
 
@@ -39,13 +39,13 @@ from kallithea.config.routing import url
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasPermissionAnyDecorator, LoginRequired
 
from kallithea.lib.base import BaseController, render
 
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
 
from kallithea.model.meta import Session
 
from kallithea.model.notification import EmailNotificationModel
 
from kallithea.model.scm import ScmModel
 
@@ -117,12 +117,13 @@ class SettingsController(BaseController)
 

	
 
                sett = Ui.get_or_create('extensions', 'hgsubversion')
 
                sett.ui_active = form_result['extensions_hgsubversion']
 
                if sett.ui_active:
 
                    try:
 
                        import hgsubversion  # pragma: no cover
 
                        assert hgsubversion
 
                    except ImportError:
 
                        raise HgsubversionImportError
 

	
 
#                sett = Ui.get_or_create('extensions', 'hggit')
 
#                sett.ui_active = form_result['extensions_hggit']
 

	
 
@@ -165,16 +166,16 @@ class SettingsController(BaseController)
 
            filesystem_repos = ScmModel().repo_scan()
 
            added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
 
                                            install_git_hooks=install_git_hooks,
 
                                            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')
 

	
 
            if invalidate_cache:
 
                log.debug('invalidating all repositories cache')
 
@@ -420,13 +421,13 @@ class SettingsController(BaseController)
 
        defaults = Setting.get_app_settings()
 
        defaults.update(self._get_hg_ui_settings())
 

	
 
        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(
 
            render('admin/settings/settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
kallithea/controllers/admin/user_groups.py
Show inline comments
 
@@ -29,25 +29,24 @@ import logging
 
import traceback
 

	
 
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
 
from kallithea.model.scm import UserGroupList
 
from kallithea.model.user_group import UserGroupModel
 

	
 
@@ -58,13 +57,12 @@ log = logging.getLogger(__name__)
 
class UserGroupsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 

	
 
    @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),
 
                                     key=lambda u: u.username.lower())
 

	
 
        c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
 
@@ -85,26 +83,24 @@ class UserGroupsController(BaseControlle
 
    def index(self, format='html'):
 
        _list = UserGroup.query() \
 
                        .order_by(func.lower(UserGroup.users_group_name)) \
 
                        .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,
 
                                              user_gr.users_group_name),
 
                "desc": h.escape(user_gr.user_group_description),
 
                "members": len(user_gr.members),
 
@@ -160,13 +156,13 @@ class UserGroupsController(BaseControlle
 
    @HasUserGroupPermissionLevelDecorator('admin')
 
    def update(self, id):
 
        c.user_group = UserGroup.get_or_404(id)
 
        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(),
 
                                         available_members=available_members)()
 

	
 
        try:
kallithea/controllers/admin/users.py
Show inline comments
 
@@ -28,13 +28,13 @@ Original author and date, and relevant c
 
import logging
 
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
 

	
 
import kallithea
 
from kallithea.config.routing import url
 
@@ -60,34 +60,32 @@ class UsersController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 

	
 
    @LoginRequired()
 
    @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) \
 
                        .filter_by(is_default_user=False) \
 
                        .order_by(func.lower(User.username)) \
 
                        .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({
 
                "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
 
                "raw_name": user.username,
 
                "username": username(user.user_id, user.username),
 
@@ -387,13 +385,13 @@ class UsersController(BaseController):
 
        c.user = self._get_user_or_raise_if_default(id)
 
        c.active = 'ips'
 
        c.user_ip_map = UserIpMap.query() \
 
            .filter(UserIpMap.user == c.user).all()
 

	
 
        c.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(
 
            render('admin/users/user_edit.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
@@ -451,23 +449,23 @@ class UsersController(BaseController):
 
        try:
 
            new_ssh_key = SshKeyModel().create(c.user.user_id,
 
                                               description, public_key)
 
            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
 
    def ssh_keys_delete(self, id):
 
        c.user = self._get_user_or_raise_if_default(id)
 

	
 
        fingerprint = request.POST.get('del_public_key_fingerprint')
 
        try:
 
            SshKeyModel().delete(fingerprint, c.user.user_id)
 
            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
 
@@ -32,17 +32,17 @@ import time
 
import traceback
 
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
 

	
 

	
 
log = logging.getLogger('JSONRPC')
 

	
 

	
 
@@ -50,13 +50,13 @@ class JSONRPCError(BaseException):
 

	
 
    def __init__(self, message):
 
        self.message = message
 
        super(JSONRPCError, self).__init__()
 

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

	
 

	
 
class JSONRPCErrorResponse(Response, HTTPException):
 
    """
 
    Generate a Response object with a JSON-RPC error body
 
    """
 
@@ -118,13 +118,13 @@ class JSONRPCController(TGController):
 
            raise JSONRPCErrorResponse(retid=self._req_id,
 
                                       message="Content-Length is 0")
 

	
 
        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,
 
                                       message="JSON parse error ERR:%s RAW:%r"
 
                                                % (e, raw_body))
 

	
 
@@ -163,27 +163,27 @@ class JSONRPCController(TGController):
 
        except AttributeError as e:
 
            raise JSONRPCErrorResponse(retid=self._req_id,
 
                                       message=str(e))
 

	
 
        # now that we have a method, add self._req_params to
 
        # self.kargs and dispatch control to WGIController
 
        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
 
        # api_key, which is translated to instance of user at that name
 
        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
 
                continue
 

	
 
            # skip the required param check if it's default value is
 
@@ -206,13 +206,13 @@ class JSONRPCController(TGController):
 
        self._rpc_args.update(self._request_params)
 
        self._rpc_args['action'] = self._req_method
 
        self._rpc_args['environ'] = environ
 

	
 
        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, [])
 
        state.set_params(self._rpc_args)
 
        return state
 

	
 
@@ -223,34 +223,34 @@ class JSONRPCController(TGController):
 
        raw_response = ''
 
        try:
 
            raw_response = getattr(self, action)(**rpc_args)
 
            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):
 
        """
 
        Return method named by `self._req_method` in controller if able
 
        """
 
        log.debug('Trying to find JSON-RPC method: %s', self._req_method)
kallithea/controllers/api/api.py
Show inline comments
 
@@ -29,14 +29,14 @@ import logging
 
import traceback
 
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
 
from kallithea.lib.vcs.backends.base import EmptyChangeset
 
from kallithea.lib.vcs.exceptions import EmptyRepositoryError
 
from kallithea.model.changeset_status import ChangesetStatusModel
 
@@ -430,13 +430,13 @@ class ApiController(JSONRPCController):
 
                .order_by(User.username)
 
                .filter_by(is_default_user=False)
 
        ]
 

	
 
    @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('')):
 
        """
 
        Creates new user. Returns new user object. This command can
 
        be executed only using api_key belonging to user with admin rights.
 
@@ -683,13 +683,13 @@ class ApiController(JSONRPCController):
 
        return [
 
            user_group.get_api_data()
 
            for user_group in UserGroupList(UserGroup.query().all(), perm_level='read')
 
        ]
 

	
 
    @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
 
        belonging to user with admin rights or an user who has create user group
 
        permission
 

	
 
@@ -1157,13 +1157,13 @@ class ApiController(JSONRPCController):
 
                'files': _f,
 
                'dirs': _d,
 
            }
 
            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(
 
                'failed to get repo: `%s` nodes' % repo.repo_name
 
            )
 

	
 
@@ -2336,13 +2336,13 @@ class ApiController(JSONRPCController):
 
                                                 end,
 
                                                 datetime.strptime(start_date, format) if start_date else None,
 
                                                 datetime.strptime(end_date, format) if end_date else None,
 
                                                 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)):
 
        repo = get_repo_or_error(repoid)
 
        if not HasRepoPermissionLevel('read')(repo.repo_name):
 
            raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
 
@@ -2370,13 +2370,13 @@ class ApiController(JSONRPCController):
 
            raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
 
        if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
 
            raise JSONRPCError('not allowed')
 
        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.
 
        """
 
        apiuser = get_user_or_error(request.authuser.user_id)
 
        pull_request = PullRequest.get(pull_request_id)
 
        if pull_request is None:
 
@@ -2397,13 +2397,13 @@ class ApiController(JSONRPCController):
 
            text=comment_msg,
 
            repo=pull_request.org_repo.repo_id,
 
            author=apiuser.user_id,
 
            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,
 
                      'user_commented_pull_request:%s' % pull_request_id,
 
                      pull_request.org_repo, request.ip_addr)
 
        if status:
kallithea/controllers/changelog.py
Show inline comments
 
@@ -35,14 +35,14 @@ from webob.exc import HTTPBadRequest, HT
 

	
 
import kallithea.lib.helpers as h
 
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
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
@@ -64,13 +64,13 @@ class ChangelogController(BaseRepoContro
 
        try:
 
            return c.db_repo_scm_instance.get_changeset(rev)
 
        except EmptyRepositoryError as e:
 
            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)
 
    @HasRepoPermissionLevelDecorator('read')
 
    def index(self, repo_name, revision=None, f_path=None):
 
        limit = 2000
 
@@ -108,41 +108,40 @@ class ChangelogController(BaseRepoContro
 
                except (NodeDoesNotExistError, ChangesetError):
 
                    # this node is not present at tip !
 
                    try:
 
                        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]
 
        c.jsdata = graph_data(c.db_repo_scm_instance, revs)
 

	
 
        c.revision = revision # requested revision ref
kallithea/controllers/changeset.py
Show inline comments
 
@@ -22,28 +22,29 @@ Original author and date, and relevant c
 
:created_on: Apr 25, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import binascii
 
import logging
 
import traceback
 
from collections import OrderedDict, defaultdict
 

	
 
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
 
from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 
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
 
from kallithea.model.comment import ChangesetCommentsModel
 
from kallithea.model.db import ChangesetComment, ChangesetStatus
 
from kallithea.model.meta import Session
 
@@ -62,13 +63,13 @@ def anchor_url(revision, path, GET):
 
    fid = h.FID(revision, path)
 
    return h.url.current(anchor=fid, **dict(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])
 
        except ValueError:
 
            raise HTTPBadRequest()
 
    return ig_ws_global
 
@@ -105,15 +106,15 @@ def _ignorews_url(GET, fileid=None):
 
    return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'})
 

	
 

	
 
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]
 

	
 
    if ln_ctx:
 
        retval = ln_ctx[0].split(':')[-1]
 
@@ -211,13 +212,12 @@ def create_cs_pr_comment(repo_name, revi
 
            Session().commit()
 
            h.flash(_('Successfully deleted pull request %s') % pull_request_id,
 
                    category='success')
 
            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()
 

	
 
    comment = ChangesetCommentsModel().create(
 
        text=text,
 
@@ -253,13 +253,13 @@ def create_cs_pr_comment(repo_name, revi
 
                      'user_closed_pull_request:%s' % pull_request_id,
 
                      c.db_repo, request.ip_addr)
 

	
 
    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
 
        data.update(comment.get_dict())
 
        data.update({'rendered_text':
 
                     render('changeset/changeset_comment_block.html')})
 
@@ -392,20 +392,22 @@ class ChangesetController(BaseRepoContro
 
                c.inline_cnt += len(comments)
 

	
 
        if len(c.cs_ranges) == 1:
 
            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' \
 
                                            % revision[:12]
 
            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'
 
            return raw_diff
 
        elif method == 'show':
 
            if len(c.cs_ranges) == 1:
kallithea/controllers/compare.py
Show inline comments
 
@@ -27,26 +27,26 @@ 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 _
 
from webob.exc import HTTPBadRequest, HTTPFound, HTTPNotFound
 

	
 
from kallithea.config.routing import url
 
from kallithea.controllers.changeset import _context_url, _ignorews_url
 
from kallithea.lib import diffs
 
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
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
@@ -94,77 +94,78 @@ class CompareController(BaseRepoControll
 
            org_changesets = []
 
            other_changesets = []
 

	
 
        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
 

	
 
            # no remote compare do it on the same repository
 
            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:
 
                from dulwich.repo import Repo
 
                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:
 
                    ancestors = [other_changesets[0].parents[0].raw_id]
 
                else:
 
                    # no changesets from other repo, ancestor is the other_rev
 
                    ancestors = [other_rev]
 

	
 
                gitrepo.close()
 
                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]]
 
            org_changesets = []
 

	
 
        else:
 
@@ -274,13 +275,13 @@ class CompareController(BaseRepoControll
 
        log.debug('running diff between %s and %s in %s',
 
                  rev1, c.cs_rev, org_repo.scm_instance.path)
 
        raw_diff = diffs.get_diff(org_repo.scm_instance, rev1=rev1, rev2=c.cs_rev,
 
                                      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
 
        c.lines_deleted = 0
 
        for f in diff_processor.parsed:
 
            st = f['stats']
kallithea/controllers/error.py
Show inline comments
 
@@ -22,13 +22,13 @@ Original author and date, and relevant c
 
:created_on: Dec 8, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
import cgi
 
import html
 
import logging
 

	
 
from tg import config, expose, request
 
from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 

	
 
@@ -61,14 +61,13 @@ class ErrorController(BaseController):
 

	
 
        e = request.environ
 
        c.serv_p = r'%(protocol)s://%(host)s/' % {
 
            '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')
 
            c.error_explanation = _('Unknown error')
 

	
 
        return dict()
kallithea/controllers/feed.py
Show inline comments
 
@@ -29,29 +29,25 @@ Original author and date, and relevant c
 
import logging
 

	
 
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)
 
    @HasRepoPermissionLevelDecorator('read')
 
    def _before(self, *args, **kwargs):
 
        super(FeedController, self)._before(*args, **kwargs)
 
@@ -95,67 +91,44 @@ class FeedController(BaseRepoController)
 
        desc_msg.append('<pre>')
 
        desc_msg.append(h.urlify_text(cs.message))
 
        desc_msg.append('\n')
 
        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,
 
                              description=''.join(self.__get_desc(cs)),
 
                              pubdate=cs.date,
 
                              )
 
                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
 
@@ -46,16 +46,16 @@ from kallithea.lib.auth import HasRepoPe
 
from kallithea.lib.base import BaseRepoController, jsonify, render
 
from kallithea.lib.exceptions import NonRelativePathError
 
from kallithea.lib.utils import action_logger
 
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
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 
@@ -87,13 +87,13 @@ class FilesController(BaseRepoController
 
            raise HTTPNotFound()
 
        except (ChangesetDoesNotExistError, LookupError):
 
            msg = _('Such revision does not exist for this repository')
 
            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):
 
        """
 
        Returns file_node or raise HTTP error.
 

	
 
@@ -107,13 +107,13 @@ class FilesController(BaseRepoController
 
                raise RepositoryError('given path is a directory')
 
        except ChangesetDoesNotExistError:
 
            msg = _('Such revision does not exist for this repository')
 
            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
 

	
 
    @LoginRequired(allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
@@ -160,25 +160,25 @@ class FilesController(BaseRepoController
 
            if c.file.is_submodule():
 
                raise HTTPFound(location=c.file.url)
 
            elif c.file.is_file():
 
                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:
 
                    c.file_history, _hist = self._get_node_history(c.changeset, f_path)
 

	
 
                c.authors = []
 
                for a in set([x.author for x in _hist]):
 
                    c.authors.append((h.email(a), h.person(a)))
 
            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'):
 
            return render('files/files_ypjax.html')
 

	
 
        # TODO: tags and bookmarks?
 
@@ -229,14 +229,14 @@ class FilesController(BaseRepoController
 
    @LoginRequired(allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
    def rawfile(self, repo_name, revision, f_path):
 
        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
 

	
 
    @LoginRequired(allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
@@ -274,14 +274,13 @@ class FilesController(BaseRepoController
 
                # binary.This might lead to erroneous text display in some
 
                # cases, but helps in other cases, like with text files
 
                # without extension.
 
                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
 
        return file_node.content
 

	
 
    @LoginRequired()
 
@@ -289,13 +288,13 @@ class FilesController(BaseRepoController
 
    def delete(self, repo_name, revision, f_path):
 
        repo = c.db_repo
 
        # check if revision is a branch identifier- basically we cannot
 
        # 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',
 
                                  repo_name=repo_name, revision='tip',
 
                                  f_path=f_path))
 

	
 
@@ -343,13 +342,13 @@ class FilesController(BaseRepoController
 
    def edit(self, repo_name, revision, f_path):
 
        repo = c.db_repo
 
        # check if revision is a branch identifier- basically we cannot
 
        # 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',
 
                                  repo_name=repo_name, revision='tip',
 
                                  f_path=f_path))
 

	
 
@@ -362,14 +361,13 @@ class FilesController(BaseRepoController
 
            raise HTTPFound(location=url('files_home', repo_name=c.repo_name,
 
                            revision=c.cs.raw_id, f_path=f_path))
 
        c.default_message = _('Edited file %s via Kallithea') % (f_path)
 
        c.f_path = f_path
 

	
 
        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
 
            mode = detect_mode(first_line, 0)
 
            content = convert_line_endings(r_post.get('content', ''), mode)
 

	
 
@@ -506,14 +504,13 @@ class FilesController(BaseRepoController
 
            return _('Empty repository')
 
        except (ImproperArchiveTypeError, KeyError):
 
            return _('Unknown archive type')
 

	
 
        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
 
        archive_cache_dir = CONFIG.get('archive_cache_dir')
 
        if archive_cache_dir and not subrepos: # TODO: subrepo caching?
 
            if not os.path.isdir(archive_cache_dir):
kallithea/controllers/forks.py
Show inline comments
 
@@ -32,19 +32,20 @@ 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
 

	
 
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
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -73,13 +74,13 @@ class ForksController(BaseRepoController
 
        repo = c.db_repo.scm_instance
 

	
 
        if c.repo_info is None:
 
            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()
 

	
 
        if c.repo_info.stats:
 
            last_rev = c.repo_info.stats.stat_on_revision + 1
kallithea/controllers/home.py
Show inline comments
 
@@ -34,13 +34,13 @@ from tg import tmpl_context as c
 
from tg.i18n import ugettext as _
 
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
 

	
 

	
 
log = logging.getLogger(__name__)
 
@@ -64,15 +64,13 @@ class HomeController(BaseController):
 

	
 
        return render('/index.html')
 

	
 
    @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()
 
            repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
 

	
 
            res = [{
 
@@ -93,23 +91,22 @@ class HomeController(BaseController):
 
                        'type': 'repo',
 
                        'obj': obj.get_dict()}
 
                       for obj in repo_iter
 
                    ],
 
                   }]
 

	
 
            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()
 

	
 
    @LoginRequired(allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
    @jsonify
 
@@ -117,31 +114,31 @@ class HomeController(BaseController):
 
        repo = Repository.get_by_repo_name(repo_name).scm_instance
 
        res = []
 
        _branches = repo.branches.items()
 
        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,
 
            'results': res
 
        }
 
        return data
kallithea/controllers/journal.py
Show inline comments
 
@@ -20,30 +20,28 @@ Journal controller
 
This file was forked by the Kallithea project in July 2014.
 
Original author and date, and relevant copyright and licensing information is below:
 
:created_on: Nov 21, 2010
 
:author: marcink
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 

	
 
"""
 

	
 
import logging
 
import traceback
 
from itertools import groupby
 

	
 
from sqlalchemy import or_
 
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
 
from kallithea.lib.utils2 import AttributeDict, safe_int
 
from kallithea.model.db import Repository, User, UserFollowing, UserLog
 
from kallithea.model.meta import Session
 
@@ -102,95 +100,70 @@ class JournalController(BaseController):
 
                        .order_by(UserLog.action_date.desc())
 
        else:
 
            journal = []
 

	
 
        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:
 
                # 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)
 
            entries.append(dict(
 
                title=title,
 
                pubdate=entry.action_date,
 
                link=_url or h.canonical_url(''),
 
                author_email=user.email,
 
                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):
 
        # Return a rendered template
 
        p = safe_int(request.GET.get('page'), 1)
 
        c.user = User.get(request.authuser.user_id)
 
@@ -198,16 +171,14 @@ class JournalController(BaseController):
 
            .filter(UserFollowing.user_id == request.authuser.user_id) \
 
            .options(joinedload(UserFollowing.follows_repository)) \
 
            .all()
 

	
 
        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'):
 
            return render('journal/journal_data.html')
 

	
 
        repos_list = Repository.query(sorted=True) \
 
@@ -218,26 +189,22 @@ class JournalController(BaseController):
 
        c.data = repos_data
 

	
 
        return render('journal/journal.html')
 

	
 
    @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)) \
 
            .all()
 
        return self._atom_feed(following, public=False)
 

	
 
    @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)) \
 
            .all()
 
        return self._rss_feed(following, public=False)
 

	
 
@@ -287,27 +254,23 @@ class JournalController(BaseController):
 
            return render('journal/journal_data.html')
 

	
 
        return render('journal/public_journal.html')
 

	
 
    @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)) \
 
            .all()
 

	
 
        return self._atom_feed(c.following)
 

	
 
    @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)) \
 
            .all()
 

	
 
        return self._rss_feed(c.following)
kallithea/controllers/login.py
Show inline comments
 
@@ -38,13 +38,12 @@ from webob.exc import HTTPBadRequest, HT
 

	
 
import kallithea.lib.helpers as h
 
from kallithea.config.routing import url
 
from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
 
from kallithea.lib.base import BaseController, log_in_user, render
 
from kallithea.lib.exceptions import UserCreationError
 
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
 
from kallithea.model.user import UserModel
 

	
 

	
 
@@ -65,28 +64,29 @@ class LoginController(BaseController):
 
        with the exception that '%' escapes are not validated and '#' is
 
        allowed inside the fragment part.
 
        """
 
        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)
 
                raise HTTPBadRequest()
 
        else:
 
            c.came_from = url('home')
 

	
 
        if request.POST:
 
            # 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
 
                defaults.pop('password', None)
 
                return htmlfill.render(
 
                    render('/login.html'),
 
@@ -99,15 +99,17 @@ class LoginController(BaseController):
 
                # container auth or other auth functions that create users on
 
                # the fly can throw this exception signaling that there's issue
 
                # with user creation, explanation should be provided in
 
                # 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?
 
                raise HTTPFound(location=c.came_from)
 
                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:
 
                raise HTTPFound(location=c.came_from)
 
            # continue to show login to default user
 

	
kallithea/controllers/pullrequests.py
Show inline comments
 
@@ -26,12 +26,13 @@ Original author and date, and relevant c
 
"""
 

	
 
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 _
 
from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPFound, HTTPNotFound
 

	
 
from kallithea.config.routing import url
 
@@ -39,16 +40,14 @@ from kallithea.controllers.changeset imp
 
from kallithea.lib import diffs
 
from kallithea.lib import helpers as h
 
from kallithea.lib.auth import HasRepoPermissionLevelDecorator, LoginRequired
 
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
 
from kallithea.model.forms import PullRequestForm, PullRequestPostForm
 
from kallithea.model.meta import Session
 
from kallithea.model.pull_request import CreatePullRequestAction, CreatePullRequestIterationAction, PullRequestModel
 
@@ -80,41 +79,34 @@ class PullrequestsController(BaseRepoCon
 
        rev: a revision that must be in the list somehow and selected by default
 
        branch: a branch that must be in the list and selected by default - even if closed
 
        branch_rev: a revision of which peers should be preferred and available."""
 
        # 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:
 
                        n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
 
                        peers.append((n, abranch))
 
                        peerbranches.add(abranch)
 

	
 
        selected = None
 
        tiprev = repo.tags.get('tip')
 
        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:
 
                tipbranch = abranch
 
                desc = '%s (current tip)' % desc
 
            branches.append((n, desc))
 
@@ -132,20 +124,20 @@ class PullrequestsController(BaseRepoCon
 
                selected = n
 
                branch = None
 
            if branch:
 
                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)
 
            tags.append((n, tag))
 
            # note: even if rev == tagrev, don't select the static tag - it must be chosen explicitly
 

	
 
@@ -170,13 +162,13 @@ class PullrequestsController(BaseRepoCon
 
                    selected = 'tag:null:' + repo.EMPTY_CHANGESET
 
                    tags.append((selected, 'null'))
 
            else:
 
                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")),
 
                  (peers, _("Peer branches")),
 
                  (bookmarks, _("Bookmarks")),
 
                  (branches, _("Branches")),
 
@@ -198,22 +190,27 @@ class PullrequestsController(BaseRepoCon
 

	
 
    @LoginRequired(allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
    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)
 
        if c.from_:
 
            q = q.filter_by(org_repo=c.db_repo)
 
        else:
 
            q = q.filter_by(other_repo=c.db_repo)
 
        c.pull_requests = q.all()
 

	
 
        c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100)
 
        c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100, **url_params)
 

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

	
 
    @LoginRequired()
 
    def show_my(self):
 
        c.closed = request.GET.get('closed') or ''
 
@@ -332,13 +329,13 @@ class PullrequestsController(BaseRepoCon
 
        description = _form['pullrequest_desc'].strip()
 
        owner = User.get(request.authuser.user_id)
 

	
 
        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:
 
            pull_request = cmd.execute()
 
            Session().commit()
 
        except Exception:
 
@@ -355,13 +352,13 @@ class PullrequestsController(BaseRepoCon
 
        owner = User.get(request.authuser.user_id)
 
        new_org_rev = self._get_ref_rev(old_pull_request.org_repo, 'rev', new_rev)
 
        new_other_rev = self._get_ref_rev(old_pull_request.other_repo, old_pull_request.other_ref_parts[0], old_pull_request.other_ref_parts[1])
 
        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:
 
            pull_request = cmd.execute()
 
            Session().commit()
 
        except Exception:
 
@@ -528,20 +525,15 @@ class PullrequestsController(BaseRepoCon
 
                        # also show changesets that not are descendants but would be merged in
 
                        targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
 
                        if org_scm_instance.path != other_scm_instance.path:
 
                            # Note: org_scm_instance.path must come first so all
 
                            # valid revision numbers are 100% org_scm compatible
 
                            # - both for avail_revs and for revset results
 
                            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',
 
                                               avail_revs, revs[0], targethead))
 
                        if show:
 
                            c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name
 
@@ -585,17 +577,17 @@ class PullrequestsController(BaseRepoCon
 
        diff_limit = None if fulldiff else self.cut_off_limit
 

	
 
        # we swap org/other ref since we run a simple diff on one repo
 
        log.debug('running diff between %s and %s in %s',
 
                  c.a_rev, c.cs_rev, org_scm_instance.path)
 
        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
 
        c.lines_deleted = 0
 

	
 
        for f in diff_processor.parsed:
kallithea/controllers/search.py
Show inline comments
 
@@ -24,27 +24,25 @@ Original author and date, and relevant c
 
:copyright: (c) 2013 RhodeCode GmbH, and others.
 
:license: GPLv3, see LICENSE.md for more details.
 
"""
 

	
 
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
 

	
 
from kallithea.lib.auth import LoginRequired
 
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
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
@@ -93,15 +91,15 @@ class SearchController(BaseRepoControlle
 
                searcher = idx.searcher()
 

	
 
                qp = QueryParser(search_type, schema=schema_defn)
 
                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)
 
                    elif isinstance(query, Prefix):
 
                        highlight_items.add(query.text)
 
                    else:
 
@@ -116,23 +114,21 @@ class SearchController(BaseRepoControlle
 
                    results = searcher.search(query)
 
                    res_ln = len(results)
 
                    c.runtime = '%s results (%.3f seconds)' % (
 
                        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,
 
                                            highlight_items, repo_location),
 
                        page=p,
 
                        item_count=res_ln,
 
                        items_per_page=10,
 
                        url=url_generator
 
                        type=c.cur_type,
 
                        q=c.cur_query,
 
                    )
 

	
 
                except QueryParserError:
 
                    c.runtime = _('Invalid search query. Try quoting it.')
 
                searcher.close()
 
            except EmptyIndexError:
kallithea/controllers/summary.py
Show inline comments
 
@@ -35,20 +35,21 @@ from time import mktime
 
from beaker.cache import cache_region
 
from tg import request
 
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
 
from kallithea.model.db import Statistics
 

	
 

	
 
@@ -62,13 +63,13 @@ README_FILES = [''.join([x[0][0], x[1][0
 
class SummaryController(BaseRepoController):
 

	
 
    def __get_readme_data(self, db_repo):
 
        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
 
            try:
 
                # gets the landing revision! or tip if fails
 
                cs = db_repo.get_landing_changeset()
 
@@ -80,13 +81,13 @@ class SummaryController(BaseRepoControll
 
                        readme = cs.get_node(f)
 
                        if not isinstance(readme, FileNode):
 
                            continue
 
                        readme_file = f
 
                        log.debug('Found README file `%s` rendering...',
 
                                  readme_file)
 
                        readme_data = renderer.render(readme.content,
 
                        readme_data = renderer.render(safe_str(readme.content),
 
                                                      filename=f)
 
                        break
 
                    except NodeDoesNotExistError:
 
                        continue
 
            except ChangesetError:
 
                log.error(traceback.format_exc())
 
@@ -101,14 +102,18 @@ class SummaryController(BaseRepoControll
 

	
 
    @LoginRequired(allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
    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)
 

	
 
        c.ssh_repo_url = None
 
        if request.authuser.is_default_user:
 
@@ -130,23 +135,19 @@ class SummaryController(BaseRepoControll
 
            .filter(Statistics.repository == c.db_repo) \
 
            .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)
 

	
 
            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
 
        c.readme_data, c.readme_file = \
 
            self.__get_readme_data(c.db_repo)
 
        return render('summary/summary.html')
 
@@ -168,13 +169,13 @@ class SummaryController(BaseRepoControll
 
            c.no_data_msg = _('No data ready yet')
 
        else:
 
            c.show_stats = False
 
            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())
 
        ts_min_y = mktime(td_1y.timetuple())
 
        ts_max_y = mktime(td.timetuple())
 
        c.ts_min = ts_min_m
 
@@ -182,35 +183,32 @@ class SummaryController(BaseRepoControll
 

	
 
        stats = Statistics.query() \
 
            .filter(Statistics.repository == c.db_repo) \
 
            .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
 
            if last_rev == 0 or c.repo_last_rev == 0:
 
                pass
 
            else:
 
                c.stats_percentage = '%.2f' % ((float((last_rev)) /
 
                                                c.repo_last_rev) * 100)
 
        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)
 
        return render('summary/statistics.html')

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

0 comments (0 inline, 0 general)