diff --git a/.bumpversion.cfg b/.bumpversion.cfg
deleted file mode 100644
--- a/.bumpversion.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[bumpversion]
-current_version = 5.4.0
-message = release: Bump version {current_version} to {new_version}
-
-[bumpversion:file:rhodecode/VERSION]
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -54,7 +54,7 @@ clean:
# test: run test-clean and tests
test:
make test-clean
- unset RC_SQLALCHEMY_DB1_URL && unset RC_DB_URL && make test-only
+ make test-only
.PHONY: test-clean
@@ -71,7 +71,25 @@ test-only:
PYTHONHASHSEED=random \
py.test -x -vv -r xw -p no:sugar \
--cov-report=term-missing --cov-report=html \
- --cov=rhodecode rhodecode
+ --cov=rhodecode rhodecode \
+ --ignore=rhodecode/tests/vcs_operations \
+ --ignore=rhodecode/tests/database
+
+ PYTHONHASHSEED=random \
+ py.test -x -vv -r xw -p no:sugar \
+ rhodecode/tests/vcs_operations
+
+ PYTHONHASHSEED=random \
+ py.test -x -vv -r xw -p no:sugar \
+ rhodecode/tests/database
+
+.PHONY: test-simple
+# test-simple: Run tests only for main test suite witout coverage
+test-simple:
+ PYTHONHASHSEED=random \
+ py.test -x -vv -r xw -p no:sugar \
+ --ignore=rhodecode/tests/vcs_operations \
+ --ignore=rhodecode/tests/database
# >>> Docs commands
diff --git a/configs/development.ini b/configs/development.ini
--- a/configs/development.ini
+++ b/configs/development.ini
@@ -485,6 +485,10 @@ rc_cache.cache_general.expiration_time =
; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
#rc_cache.cache_general.arguments.lock_auto_renewal = true
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_general.arguments.key_prefix = custom-prefix-
+
+
; *************************************************
; `cache_perms` cache for permission tree, auth TTL
; for simplicity use rc.file_namespace backend,
@@ -512,6 +516,10 @@ rc_cache.cache_perms.expiration_time = 3
; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
#rc_cache.cache_perms.arguments.lock_auto_renewal = true
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_perms.arguments.key_prefix = custom-prefix-
+
+
; ***************************************************
; `cache_repo` cache for file tree, Readme, RSS FEEDS
; for simplicity use rc.file_namespace backend,
@@ -539,6 +547,40 @@ rc_cache.cache_repo.expiration_time = 25
; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
#rc_cache.cache_repo.arguments.lock_auto_renewal = true
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_repo.arguments.key_prefix = custom-prefix-
+
+; *********************************************
+; `cache_license` cache for storing license info
+; for simplicity use rc.file_namespace backend,
+; for performance and scale use rc.redis
+; *********************************************
+rc_cache.cache_license.backend = dogpile.cache.rc.file_namespace
+rc_cache.cache_license.expiration_time = 300
+; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
+#rc_cache.cache_license.arguments.filename = /tmp/cache_general_db
+
+; alternative `cache_license` redis backend with distributed lock
+#rc_cache.cache_license.backend = dogpile.cache.rc.redis
+#rc_cache.cache_license.expiration_time = 300
+
+; redis_expiration_time needs to be greater then expiration_time
+#rc_cache.cache_license.arguments.redis_expiration_time = 360
+
+#rc_cache.cache_license.arguments.host = localhost
+#rc_cache.cache_license.arguments.port = 6379
+#rc_cache.cache_license.arguments.db = 0
+#rc_cache.cache_license.arguments.socket_timeout = 30
+; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
+#rc_cache.cache_license.arguments.distributed_lock = true
+
+; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
+#rc_cache.cache_license.arguments.lock_auto_renewal = true
+
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_license.arguments.key_prefix = custom-prefix-
+
+
; ##############
; BEAKER SESSION
; ##############
@@ -547,7 +589,7 @@ rc_cache.cache_repo.expiration_time = 25
; types are file, ext:redis, ext:database, ext:memcached
; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
#beaker.session.type = file
-#beaker.session.data_dir = %(here)s/data/sessions
+#beaker.session.data_dir = /var/opt/rhodecode_data/sessions
; Redis based sessions
beaker.session.type = ext:redis
diff --git a/configs/production.ini b/configs/production.ini
--- a/configs/production.ini
+++ b/configs/production.ini
@@ -453,6 +453,10 @@ rc_cache.cache_general.expiration_time =
; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
#rc_cache.cache_general.arguments.lock_auto_renewal = true
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_general.arguments.key_prefix = custom-prefix-
+
+
; *************************************************
; `cache_perms` cache for permission tree, auth TTL
; for simplicity use rc.file_namespace backend,
@@ -480,6 +484,10 @@ rc_cache.cache_perms.expiration_time = 3
; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
#rc_cache.cache_perms.arguments.lock_auto_renewal = true
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_perms.arguments.key_prefix = custom-prefix-
+
+
; ***************************************************
; `cache_repo` cache for file tree, Readme, RSS FEEDS
; for simplicity use rc.file_namespace backend,
@@ -507,6 +515,40 @@ rc_cache.cache_repo.expiration_time = 25
; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
#rc_cache.cache_repo.arguments.lock_auto_renewal = true
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_repo.arguments.key_prefix = custom-prefix-
+
+; *********************************************
+; `cache_license` cache for storing license info
+; for simplicity use rc.file_namespace backend,
+; for performance and scale use rc.redis
+; *********************************************
+rc_cache.cache_license.backend = dogpile.cache.rc.file_namespace
+rc_cache.cache_license.expiration_time = 300
+; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
+#rc_cache.cache_license.arguments.filename = /tmp/cache_general_db
+
+; alternative `cache_license` redis backend with distributed lock
+#rc_cache.cache_license.backend = dogpile.cache.rc.redis
+#rc_cache.cache_license.expiration_time = 300
+
+; redis_expiration_time needs to be greater then expiration_time
+#rc_cache.cache_license.arguments.redis_expiration_time = 360
+
+#rc_cache.cache_license.arguments.host = localhost
+#rc_cache.cache_license.arguments.port = 6379
+#rc_cache.cache_license.arguments.db = 0
+#rc_cache.cache_license.arguments.socket_timeout = 30
+; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
+#rc_cache.cache_license.arguments.distributed_lock = true
+
+; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
+#rc_cache.cache_license.arguments.lock_auto_renewal = true
+
+; prefix for redis keys used for this cache backend, the final key is constructed using {custom-prefix}{key}
+#rc_cache.cache_license.arguments.key_prefix = custom-prefix-
+
+
; ##############
; BEAKER SESSION
; ##############
@@ -515,7 +557,7 @@ rc_cache.cache_repo.expiration_time = 25
; types are file, ext:redis, ext:database, ext:memcached
; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
#beaker.session.type = file
-#beaker.session.data_dir = %(here)s/data/sessions
+#beaker.session.data_dir = /var/opt/rhodecode_data/sessions
; Redis based sessions
beaker.session.type = ext:redis
diff --git a/conftest.py b/conftest.py
--- a/conftest.py
+++ b/conftest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -27,8 +27,11 @@ from rhodecode.tests.conftest_common imp
pytest_plugins = [
- "rhodecode.tests.fixture_mods.fixture_pyramid",
- "rhodecode.tests.fixture_mods.fixture_utils",
+ "rhodecode.tests.fixtures.fixture_pyramid",
+ "rhodecode.tests.fixtures.fixture_utils",
+ "rhodecode.tests.fixtures.function_scoped_baseapp",
+ "rhodecode.tests.fixtures.module_scoped_baseapp",
+ "rhodecode.tests.fixtures.rcextensions_fixtures",
]
diff --git a/docs/.howto b/docs/.howto
--- a/docs/.howto
+++ b/docs/.howto
@@ -3,4 +3,4 @@
# docker build --tag sphinx-doc-build-rc .
# Build Docs
-# docker run --rm -v $(PWD):/project --workdir=/project/docs sphinx-doc-build-rc make clean html
\ No newline at end of file
+# cd && docker run --rm -v $(pwd):/project --workdir=/project/docs sphinx-doc-build-rc make clean html
diff --git a/docs/install/migrate-repos.rst b/docs/install/migrate-repos.rst
--- a/docs/install/migrate-repos.rst
+++ b/docs/install/migrate-repos.rst
@@ -2,22 +2,24 @@
Migrating |repos|
-----------------
-
-If you have installed |RCE| and have |repos| that you wish to migrate into
-the system, use the following instructions.
+When you install |RCE| you will be presented with 2 choices:
-1. On the |RCE| interface, check your |repo| storage location under
- :menuselection:`Admin --> Settings --> System Info`. For example,
- Storage location: /home/{username}/repos.
+1. Mount existing folder on your local filesystem, and this is what |RCE| will use to store your repos. In this case |RCE| will automatically import repos present in that folder. You can also do :ref:`remap-rescan` from admin interface to force new repos discovery.
+2. Use docker named volume ``rc_reposvolume`` - in this case you can either set create new |repo| in |RCE| UI and set it as a remote for your local repo, and then push. If your repo is hosted somewhere else, click "Import Existing Repository ?" and provide url to it. |RCE| will attempt to pull it and import.
-2. Copy the |repos| that you want |RCE| to manage to this location.
-3. Remap and rescan the |repos|, see :ref:`remap-rescan`
.. important::
+ Repo storage path inside docker is configurable in ``rhodecode.ini`` file:
+ ``repo_store.path = /var/opt/rhodecode_repo_store``
- Directories create |repo| groups inside |RCE|.
+ Mounting of local filesystem folder is configurable in ``docker-compose-apps.override.yaml``
+
+ Importing adds |RCE| git hooks to your |repos|.
- Importing adds |RCE| git hooks to your |repos|.
+ You should verify if custom ``.hg`` or ``.hgrc`` files inside
+ repositories should be adjusted since |RCE| reads the content of them.
- You should verify if custom ``.hg`` or ``.hgrc`` files inside
- repositories should be adjusted since |RCE| reads the content of them.
+.. note::
+ There's another override file for dev setup, which mounts default workspace folder as a repo storage by default.
+ you can configure this by changing ``docker-compose-apps.dev.yaml``
+ (and commenting out all lines ``$WORKSPACE_HOME:/var/opt/rhodecode_repo_store:delegated``)
diff --git a/docs/release-notes/release-notes-5.3.1.rst b/docs/release-notes/release-notes-5.3.1.rst
new file mode 100644
--- /dev/null
+++ b/docs/release-notes/release-notes-5.3.1.rst
@@ -0,0 +1,41 @@
+|RCE| 5.3.1 |RNS|
+-----------------
+
+Release Date
+^^^^^^^^^^^^
+
+- 2024-09-21
+
+
+New Features
+^^^^^^^^^^^^
+
+
+
+General
+^^^^^^^
+
+
+
+Security
+^^^^^^^^
+
+
+
+Performance
+^^^^^^^^^^^
+
+- svn: small performance bump by restoring path cache in previous builds
+
+
+Fixes
+^^^^^
+
+- Fixed problems with incorrect version string
+- Fixed accidentally removed svn path check cache
+
+
+Upgrade notes
+^^^^^^^^^^^^^
+
+- RhodeCode 5.3.1 is unscheduled security release to address some build issues with 5.X images
diff --git a/docs/release-notes/release-notes-5.4.0.rst b/docs/release-notes/release-notes-5.4.0.rst
new file mode 100644
--- /dev/null
+++ b/docs/release-notes/release-notes-5.4.0.rst
@@ -0,0 +1,49 @@
+|RCE| 5.4.0 |RNS|
+-----------------
+
+Release Date
+^^^^^^^^^^^^
+
+
+
+
+New Features
+^^^^^^^^^^^^
+
+- RhodeCode and VcsServer operate now on bytestrings instead of strings for diffs and files. This allows a mixed or non-utf8
+ encoded files to be used.
+- Remap and rescan: added more resilient remap and removal option, and also split the logic to either add or cleanup.
+- Automatic merge of approved pull requests (available only for enterprise edition).
+- Basic implementation of security scan (secrets, keys, passwords, etc) for repositories (available only for enterprise edition).
+
+General
+^^^^^^^
+
+- Ported build system to use pyproject.toml.
+- Bumped version of mercurial and hg-evolve.
+
+
+Security
+^^^^^^^^
+
+- Multiple library bumps to fix security issues found in those python packages.
+
+
+Performance
+^^^^^^^^^^^
+
+- SVN: added same caches for stored file nodes as git and mercurial have for improved performance.
+- GIT: moved certain operations into vcsserver
+
+Fixes
+^^^^^
+
+- Fixed UI visualization issues with long commit messages
+- Fixed masking for some fields in admin settings
+- Fixed issues with caches calculations when passed search fields were empty
+- Fixed observer's ability to change a PR status
+
+Upgrade notes
+^^^^^^^^^^^^^
+
+
diff --git a/docs/release-notes/release-notes.rst b/docs/release-notes/release-notes.rst
--- a/docs/release-notes/release-notes.rst
+++ b/docs/release-notes/release-notes.rst
@@ -9,6 +9,8 @@ Release Notes
.. toctree::
:maxdepth: 1
+ release-notes-5.4.0.rst
+ release-notes-5.3.1.rst
release-notes-5.3.0.rst
release-notes-5.2.1.rst
release-notes-5.2.0.rst
diff --git a/docs/requirements_docs.txt b/docs/requirements_docs.txt
--- a/docs/requirements_docs.txt
+++ b/docs/requirements_docs.txt
@@ -8,4 +8,4 @@ pygments==2.18.0
docutils<0.19
markupsafe==2.1.3
-jinja2==3.1.2
+jinja2==3.1.4
diff --git a/docs/tools/tools-cli.rst b/docs/tools/tools-cli.rst
--- a/docs/tools/tools-cli.rst
+++ b/docs/tools/tools-cli.rst
@@ -12,6 +12,26 @@ The commands available with |RCT| can be
- Local configuration commands used to help set up your |RCT| configuration.
+.. _tools-rhodecode-list-instance:
+
+rhodecode-list-instances
+------------------------
+
+Use this command to list the instance details configured in the
+:file:`/etc/rhodecode/conf/.rhoderc` file.
+
+.. code-block:: bash
+
+ $ ./rcstack cli cmd rhodecode-list-instances --config=/etc/rhodecode/conf/.rhoderc
+ [instance:production] - Config only
+ API-HOST: https://some.url.com
+ API-KEY: some.auth.token
+
+ [instance:development] - Config only
+ API-HOST: http://some.ip.address
+ API-KEY: some.auth.token
+
+
rhodecode-tools
---------------
@@ -163,6 +183,7 @@ Example usage:
removing gist /home/brian/repos/.rc_gist_store/5
removing gist /home/brian/repos/.rc_gist_store/8FtCKdcbRKmEvRzTVsEt
+
rhodecode-cleanup-repos
-----------------------
@@ -201,30 +222,37 @@ Example usage:
.. code-block:: bash
- # Cleaning up repos using tools installed with RCE 350 and above
- $ ~/.rccontrol/enterprise-4/profile/bin/rhodecode-cleanup-repos \
- --instance-name=enterprise-4 --older-than=1d
- Scanning for repositories in /home/brian/repos...
- preparing to remove [2] found repositories older than 1 day, 0:00:00 (1d)
+
+ # create a .rhoderc file in your host config directory (in :file:`config/_shared/.rhoderc`):
+
+ [instance:rcstack-instance]
+ api_host = http://rhodecode:10020
+ api_key =
+ repo_dir = /var/opt/rhodecode_repo_store
+
+ # Run rcstack cli
+ ./rcstack cli cmd rhodecode-cleanup-repos --instance-name=rcstack-instance --config=/etc/rhodecode/conf/.rhoderc
+
+ checking if config files needs bootstrapping
+ Scanning for repositories in /var/opt/rhodecode_repo_store...
the following repositories will be deleted completely:
- * REMOVED: 2015-08-05 00:23:18 | /home/brian/repos/rm__20150805_002318_831
- * REMOVED: 2015-08-04 01:22:10 | /home/brian/repos/rm__20150804_012210_336
+ * REMOVED: 2015-08-05 00:23:18 | /var/opt/rhodecode_repo_store/rm__20150805_002318_831
+ * REMOVED: 2015-08-04 01:22:10 | /var/opt/rhodecode_repo_store/rm__20150804_012210_336
are you sure you want to remove them? [y/N]:
# Clean up repos older than 1 year
- # If using virtualenv and pre RCE 350 tools installation
- (venv)$ rhodecode-cleanup-repos --instance-name=enterprise-1 \
- --older-than=365d
+ ./rcstack cli cmd rhodecode-cleanup-repos --instance-name=rcstack-instance --config=/etc/rhodecode/conf/.rhoderc --older-than=365d
- Scanning for repositories in /home/brian/repos...
+ checking if config files needs bootstrapping
+ Scanning for repositories in /var/opt/rhodecode_repo_store...
preparing to remove [343] found repositories older than 365 days
# clean up repos older than 3 days
- # If using virtualenv and pre RCE 350 tools installation
- (venv)$ rhodecode-cleanup-repos --instance-name=enterprise-1 \
- --older-than=3d
- Scanning for repositories in /home/brian/repos...
+ ./rcstack cli cmd rhodecode-cleanup-repos --instance-name=rcstack-instance --config=/etc/rhodecode/conf/.rhoderc --older-than=3d
+
+ checking if config files needs bootstrapping
+ Scanning for repositories in /var/opt/rhodecode_repo_store...
preparing to remove [3] found repositories older than 3 days
.. _tools-config:
@@ -420,9 +448,8 @@ Example usage:
}
# Cat a file and pipe to gist
- # in RCE 3.5.0 tools and above
- $ cat ~/.rhoderc | ~/.rccontrol/{instance-id}/profile/bin/rhodecode-gist \
- --instance-name=enterprise-4 -d '.rhoderc copy' create
+
+ $ cat ~/.rhoderc | ./rcstack cli cmd rhodecode-gist --instance-name=rcstack-instance --config=/etc/rhodecode/conf/.rhoderc -d '.rhoderc copy' create
{
"error": null,
"id": 9253,
@@ -512,41 +539,18 @@ Example usage:
.. code-block:: bash
- # Run the indexer
- $ ~/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
- --instance-name=enterprise-4
+ # Create the indexing mapping file
+ $ ./rcstack cli cmd rhodecode-index --instance-name=rcstack-instance --config=/etc/rhodecode/conf/.rhoderc --create-mapping search_mapping.ini
- # Run indexer based on search_mapping.ini file
- # This is using pre-350 virtualenv
- (venv)$ rhodecode-index --instance-name=enterprise-1
+ # Run the indexer
+ $ ./rcstack cli cmd rhodecode-index --instance-name=rcstack-instance --config=/etc/rhodecode/conf/.rhoderc
+
+ # Run indexer based on search_mapping.ini file using rhodecode-tools virtualenv
+ (venv)$ rhodecode-index --instance-name=rcstack-instance
# Index from the command line without creating
# the .rhoderc file
- $ rhodecode-index --apikey=key --apihost=http://rhodecode.server \
- --instance-name=enterprise-2 --save-config
-
- # Create the indexing mapping file
- $ ~/.rccontrol/enterprise-4/profile/bin/rhodecode-index \
- --create-mapping search_mapping.ini --instance-name=enterprise-4
-
-.. _tools-rhodecode-list-instance:
-
-rhodecode-list-instances
-------------------------
-
-Use this command to list the instance details configured in the
-:file:`~/.rhoderc` file.
-
-.. code-block:: bash
-
- $ .rccontrol/enterprise-1/profile/bin/rhodecode-list-instances
- [instance:production] - Config only
- API-HOST: https://some.url.com
- API-KEY: some.auth.token
-
- [instance:development] - Config only
- API-HOST: http://some.ip.address
- API-KEY: some.auth.token
+ $ rhodecode-index --apikey=key --apihost=http://rhodecode.server --instance-name=rcstack-instance --save-config
.. _tools-setup-config:
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,137 @@
+[build-system]
+requires = ["setuptools>=61.0.0", "wheel", "pastescript"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "rhodecode-enterprise-ce"
+description = "Enterprise Source Code Management Platform"
+authors = [
+ {name = "RhodeCode GmbH", email = "support@rhodecode.com"},
+]
+keywords = [
+ 'rhodecode', 'mercurial', 'git', 'svn',
+ 'code review',
+ 'repo groups', 'ldap', 'repository management', 'hgweb',
+ 'hgwebdir', 'gitweb', 'serving hgweb',
+]
+license = {text = "GPL V3"}
+requires-python = ">=3.10"
+dynamic = ["version", "readme", "dependencies", "optional-dependencies"]
+classifiers = [
+ 'Development Status :: 6 - Mature',
+ 'Intended Audience :: Developers',
+ 'Operating System :: OS Independent',
+ 'Topic :: Software Development :: Version Control',
+ 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
+ 'Programming Language :: Python :: 3.11',
+]
+
+[project.entry-points."paste.app_factory"]
+main = "rhodecode.config.middleware:make_pyramid_app"
+
+[project.entry-points."pyramid.pshell_runner"]
+ipython = "rhodecode.lib.pyramid_shell:ipython_shell_runner"
+
+[project.entry-points."beaker.backends"]
+memorylru_base="rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerBase"
+memorylru_debug="rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerDebug"
+
+[project.scripts]
+
+rc-setup-app = "rhodecode.lib.rc_commands.setup_rc:main"
+rc-upgrade-db = "rhodecode.lib.rc_commands.upgrade_db:main"
+rc-ishell = "rhodecode.lib.rc_commands.ishell:main"
+rc-add-artifact = "rhodecode.lib.rc_commands.add_artifact:main"
+rc-migrate-artifact = "rhodecode.lib.rc_commands.migrate_artifact:main"
+rc-ssh-wrapper = "rhodecode.apps.ssh_support.lib.ssh_wrapper_v1:main"
+rc-ssh-wrapper-v2 = "rhodecode.apps.ssh_support.lib.ssh_wrapper_v2:main"
+
+[tool.setuptools]
+packages = ["rhodecode"]
+include-package-data = true
+
+[tools.setuptools.package-data]
+"" = ['*.txt', '*.rst']
+"configs" = ['*.ini']
+"rhodecode" = ['VERSION', 'i18n/*/LC_MESSAGES/*.mo', ]
+
+[tool.setuptools.exclude-package-data]
+"rhodecode" = ["__pycache__"]
+
+[tool.setuptools.dynamic]
+readme = {file = ["README.rst"], content-type = "text/rst"}
+version = {file = "rhodecode/VERSION"}
+dependencies = {file = ["requirements.txt"]}
+optional-dependencies.tests = {file = ["requirements_test.txt"]}
+
+[tool.ruff]
+# Line length max same as Black.
+line-length = 120
+
+# Assume Python 3.11
+target-version = "py311"
+
+
+[tool.ruff.lint]
+select = [
+ # Pyflakes
+ "F",
+ # Pycodestyle
+ "E",
+ "W",
+ # isort
+ "I001"
+]
+
+ignore = [
+ "E501", # line too long, handled by black
+]
+
+[tool.ruff.lint.isort]
+known-first-party = ["rhodecode"]
+
+[tool.ruff.format]
+# Like Black, use double quotes for strings.
+quote-style = "double"
+
+# Like Black, indent with spaces, rather than tabs.
+indent-style = "space"
+
+# Like Black, respect magic trailing commas.
+skip-magic-trailing-comma = false
+
+# Like Black, automatically detect the appropriate line ending.
+line-ending = "auto"
+
+
+[tool.bumpversion]
+current_version = "5.4.0"
+parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)"
+serialize = ["{major}.{minor}.{patch}"]
+search = "{current_version}"
+replace = "{new_version}"
+regex = false
+ignore_missing_version = false
+ignore_missing_files = false
+tag = false
+sign_tags = false
+tag_name = "v{new_version}"
+tag_message = "release(version-bump): {current_version} → {new_version}"
+allow_dirty = false
+commit = false
+message = "release(version-bump): {current_version} → {new_version}"
+commit_args = ""
+setup_hooks = []
+pre_commit_hooks = []
+post_commit_hooks = []
+
+
+# message_extractors={
+# 'rhodecode': [
+# ('**.py', 'python', None),
+# ('**.js', 'javascript', None),
+# ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
+# ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
+# ('public/**', 'ignore', None),
+# ]
+# },
diff --git a/pytest.ini b/pytest.ini
--- a/pytest.ini
+++ b/pytest.ini
@@ -4,8 +4,10 @@ norecursedirs = rhodecode/public rhodeco
cache_dir = /tmp/.pytest_cache
pyramid_config = rhodecode/tests/rhodecode.ini
-vcsserver_protocol = http
-vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
+
+vcsserver_config = rhodecode/tests/vcsserver_http.ini
+rhodecode_config = rhodecode/tests/rhodecode.ini
+celery_config = rhodecode/tests/rhodecode.ini
addopts =
--pdbcls=IPython.terminal.debugger:TerminalPdb
diff --git a/requirements.txt b/requirements.txt
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,39 +1,39 @@
# deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
alembic==1.13.1
- mako==1.2.4
- markupsafe==2.1.2
+ mako==1.3.6
+ markupsafe==3.0.2
sqlalchemy==1.4.52
- greenlet==3.0.3
+ greenlet==3.1.1
typing_extensions==4.12.2
async-timeout==4.0.3
babel==2.12.1
-beaker==1.12.1
-celery==5.3.6
- billiard==4.2.0
- click==8.1.3
- click-didyoumean==0.3.0
- click==8.1.3
+beaker==1.13.0
+celery==5.4.0
+ billiard==4.2.1
+ click==8.1.7
+ click-didyoumean==0.3.1
+ click==8.1.7
click-plugins==1.1.1
- click==8.1.3
- click-repl==0.2.0
- click==8.1.3
- prompt_toolkit==3.0.47
+ click==8.1.7
+ click-repl==0.3.0
+ click==8.1.7
+ prompt_toolkit==3.0.48
wcwidth==0.2.13
- six==1.16.0
- kombu==5.3.5
+ kombu==5.4.2
amqp==5.2.0
vine==5.1.0
+ tzdata==2024.2
vine==5.1.0
- python-dateutil==2.8.2
+ python-dateutil==2.9.0.post0
six==1.16.0
- tzdata==2024.1
+ tzdata==2024.2
vine==5.1.0
channelstream==0.7.1
- gevent==24.2.1
- greenlet==3.0.3
+ gevent==24.11.1
+ greenlet==3.1.1
zope.event==5.0.0
- zope.interface==7.0.3
+ zope.interface==7.1.1
itsdangerous==1.1.0
marshmallow==2.18.0
pyramid==2.0.2
@@ -43,14 +43,14 @@ channelstream==0.7.1
pastedeploy==3.1.0
plaster==1.1.2
translationstring==1.4
- venusian==3.0.0
- webob==1.8.7
+ venusian==3.1.0
+ webob==1.8.9
zope.deprecation==5.0.0
- zope.interface==7.0.3
+ zope.interface==7.1.1
pyramid-jinja2==2.10
- jinja2==3.1.2
- markupsafe==2.1.2
- markupsafe==2.1.2
+ jinja2==3.1.4
+ markupsafe==3.0.2
+ markupsafe==3.0.2
pyramid==2.0.2
hupper==1.12
plaster==1.1.2
@@ -58,18 +58,18 @@ channelstream==0.7.1
pastedeploy==3.1.0
plaster==1.1.2
translationstring==1.4
- venusian==3.0.0
- webob==1.8.7
+ venusian==3.1.0
+ webob==1.8.9
zope.deprecation==5.0.0
- zope.interface==7.0.3
+ zope.interface==7.1.1
zope.deprecation==5.0.0
- python-dateutil==2.8.2
+ python-dateutil==2.9.0.post0
six==1.16.0
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.1.0
- idna==3.4
- urllib3==1.26.14
+ requests==2.32.3
+ certifi==2024.8.30
+ charset-normalizer==3.4.0
+ idna==3.10
+ urllib3==1.26.20
ws4py==0.5.1
deform==2.0.15
chameleon==3.10.2
@@ -87,13 +87,13 @@ dogpile.cache==1.3.3
pbr==5.11.1
formencode==2.1.0
six==1.16.0
-fsspec==2024.9.0
+fsspec==2024.10.0
gunicorn==23.0.0
packaging==24.1
-gevent==24.2.1
- greenlet==3.0.3
+gevent==24.11.1
+ greenlet==3.1.1
zope.event==5.0.0
- zope.interface==7.0.3
+ zope.interface==7.1.1
ipython==8.26.0
decorator==5.1.1
jedi==0.19.1
@@ -102,7 +102,7 @@ ipython==8.26.0
traitlets==5.14.3
pexpect==4.9.0
ptyprocess==0.7.0
- prompt_toolkit==3.0.47
+ prompt_toolkit==3.0.48
wcwidth==0.2.13
pygments==2.18.0
stack-data==0.6.3
@@ -113,7 +113,7 @@ ipython==8.26.0
traitlets==5.14.3
typing_extensions==4.12.2
markdown==3.4.3
-msgpack==1.0.8
+msgpack==1.1.0
mysqlclient==2.1.1
nbconvert==7.7.3
beautifulsoup4==4.12.3
@@ -122,23 +122,23 @@ nbconvert==7.7.3
six==1.16.0
webencodings==0.5.1
defusedxml==0.7.1
- jinja2==3.1.2
- markupsafe==2.1.2
+ jinja2==3.1.4
+ markupsafe==3.0.2
jupyter_core==5.3.1
platformdirs==3.10.0
traitlets==5.14.3
jupyterlab-pygments==0.2.2
- markupsafe==2.1.2
+ markupsafe==3.0.2
mistune==2.0.5
nbclient==0.8.0
jupyter_client==8.3.0
jupyter_core==5.3.1
platformdirs==3.10.0
traitlets==5.14.3
- python-dateutil==2.8.2
+ python-dateutil==2.9.0.post0
six==1.16.0
- pyzmq==25.0.0
- tornado==6.2
+ pyzmq==26.2.0
+ tornado==6.4.2
traitlets==5.14.3
jupyter_core==5.3.1
platformdirs==3.10.0
@@ -146,7 +146,7 @@ nbconvert==7.7.3
nbformat==5.9.2
fastjsonschema==2.18.0
jsonschema==4.18.6
- attrs==22.2.0
+ attrs==24.2.0
pyrsistent==0.19.3
jupyter_core==5.3.1
platformdirs==3.10.0
@@ -156,7 +156,7 @@ nbconvert==7.7.3
nbformat==5.9.2
fastjsonschema==2.18.0
jsonschema==4.18.6
- attrs==22.2.0
+ attrs==24.2.0
pyrsistent==0.19.3
jupyter_core==5.3.1
platformdirs==3.10.0
@@ -174,20 +174,20 @@ premailer==3.10.0
cssselect==1.2.0
cssutils==2.6.0
lxml==5.3.0
- requests==2.28.2
- certifi==2022.12.7
- charset-normalizer==3.1.0
- idna==3.4
- urllib3==1.26.14
+ requests==2.32.3
+ certifi==2024.8.30
+ charset-normalizer==3.4.0
+ idna==3.10
+ urllib3==1.26.20
psutil==5.9.8
-psycopg2==2.9.9
+psycopg2==2.9.10
py-bcrypt==0.4
pycmarkgfm==1.2.0
- cffi==1.16.0
- pycparser==2.21
-pycryptodome==3.17
+ cffi==1.17.1
+ pycparser==2.22
+pycryptodome==3.21.0
pycurl==7.45.3
-pymysql==1.0.3
+pymysql==1.1.1
pyotp==2.8.0
pyparsing==3.1.1
pyramid-mailer==0.15.1
@@ -198,19 +198,19 @@ pyramid-mailer==0.15.1
pastedeploy==3.1.0
plaster==1.1.2
translationstring==1.4
- venusian==3.0.0
- webob==1.8.7
+ venusian==3.1.0
+ webob==1.8.9
zope.deprecation==5.0.0
- zope.interface==7.0.3
+ zope.interface==7.1.1
repoze.sendmail==4.4.1
transaction==5.0.0
- zope.interface==7.0.3
- zope.interface==7.0.3
+ zope.interface==7.1.1
+ zope.interface==7.1.1
transaction==5.0.0
- zope.interface==7.0.3
+ zope.interface==7.1.1
pyramid-mako==1.1.0
- mako==1.2.4
- markupsafe==2.1.2
+ mako==1.3.6
+ markupsafe==3.0.2
pyramid==2.0.2
hupper==1.12
plaster==1.1.2
@@ -218,16 +218,15 @@ pyramid-mako==1.1.0
pastedeploy==3.1.0
plaster==1.1.2
translationstring==1.4
- venusian==3.0.0
- webob==1.8.7
+ venusian==3.1.0
+ webob==1.8.9
zope.deprecation==5.0.0
- zope.interface==7.0.3
+ zope.interface==7.1.1
python-ldap==3.4.3
pyasn1==0.4.8
pyasn1-modules==0.2.8
pyasn1==0.4.8
-python-memcached==1.59
- six==1.16.0
+python-memcached==1.62
python-pam==2.0.2
python3-saml==1.16.0
isodate==0.6.1
@@ -236,60 +235,66 @@ python3-saml==1.16.0
xmlsec==1.3.14
lxml==5.3.0
pyyaml==6.0.1
-redis==5.1.0
+redis==5.2.0
async-timeout==4.0.3
regex==2022.10.31
routes==2.5.1
repoze.lru==0.7
six==1.16.0
-s3fs==2024.9.0
- aiobotocore==2.13.0
- aiohttp==3.9.5
+s3fs==2024.10.0
+ aiobotocore==2.15.2
+ aiohttp==3.11.4
+ aiohappyeyeballs==2.4.3
aiosignal==1.3.1
- frozenlist==1.4.1
- attrs==22.2.0
- frozenlist==1.4.1
- multidict==6.0.5
- yarl==1.9.4
- idna==3.4
- multidict==6.0.5
- aioitertools==0.11.0
- botocore==1.34.106
+ frozenlist==1.5.0
+ attrs==24.2.0
+ frozenlist==1.5.0
+ multidict==6.1.0
+ propcache==0.2.0
+ yarl==1.17.2
+ idna==3.10
+ multidict==6.1.0
+ propcache==0.2.0
+ aioitertools==0.12.0
+ botocore==1.35.36
jmespath==1.0.1
- python-dateutil==2.8.2
+ python-dateutil==2.9.0.post0
six==1.16.0
- urllib3==1.26.14
+ urllib3==1.26.20
wrapt==1.16.0
- aiohttp==3.9.5
+ aiohttp==3.11.4
+ aiohappyeyeballs==2.4.3
aiosignal==1.3.1
- frozenlist==1.4.1
- attrs==22.2.0
- frozenlist==1.4.1
- multidict==6.0.5
- yarl==1.9.4
- idna==3.4
- multidict==6.0.5
- fsspec==2024.9.0
+ frozenlist==1.5.0
+ attrs==24.2.0
+ frozenlist==1.5.0
+ multidict==6.1.0
+ propcache==0.2.0
+ yarl==1.17.2
+ idna==3.10
+ multidict==6.1.0
+ propcache==0.2.0
+ fsspec==2024.10.0
simplejson==3.19.2
sshpubkeys==3.3.1
- cryptography==40.0.2
- cffi==1.16.0
- pycparser==2.21
- ecdsa==0.18.0
+ cryptography==43.0.3
+ cffi==1.17.1
+ pycparser==2.22
+ ecdsa==0.19.0
six==1.16.0
sqlalchemy==1.4.52
- greenlet==3.0.3
+ greenlet==3.1.1
typing_extensions==4.12.2
supervisor==4.2.5
tzlocal==4.3
pytz-deprecation-shim==0.1.0.post0
- tzdata==2024.1
+ tzdata==2024.2
tempita==0.5.2
-unidecode==1.3.6
+unidecode==1.3.8
urlobject==2.4.3
-waitress==3.0.0
+waitress==3.0.1
webhelpers2==2.1
- markupsafe==2.1.2
+ markupsafe==3.0.2
six==1.16.0
whoosh==2.7.4
zope.cachedescriptors==5.0.0
diff --git a/requirements_debug.txt b/requirements_debug.txt
--- a/requirements_debug.txt
+++ b/requirements_debug.txt
@@ -15,10 +15,8 @@ pyramid-debugtoolbar
flake8
ruff
-pipdeptree==2.7.1
+pipdeptree
invoke==2.0.0
-bumpversion==0.6.0
-bump2version==1.0.1
docutils-stubs
types-redis
diff --git a/requirements_rc_tools.txt b/requirements_rc_tools.txt
--- a/requirements_rc_tools.txt
+++ b/requirements_rc_tools.txt
@@ -1,3 +1,3 @@
## rhodecode-tools, special case, use file://PATH.tar.gz#egg=rhodecode-tools==X.Y.Z, to test local version
-rhodecode-tools @ https://code.rhodecode.com/_file_store/download/0-36d03a63-36f7-47af-a33c-d81c39f88251.0.0.tar.gz#egg=rhodecode-tools
-rhodecode-tools==3.0.0
+rhodecode-tools @ https://code.rhodecode.com/_file_store/download/0-51fb13c9-6175-480c-b9dc-630c7601d47c.1.0.tar.gz#egg=rhodecode-tools
+rhodecode-tools==4.1.0
diff --git a/requirements_test.txt b/requirements_test.txt
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,47 +1,54 @@
# test related requirements
+
mock==5.1.0
-pytest-cov==4.1.0
- coverage==7.4.3
- pytest==8.1.1
- iniconfig==2.0.0
- packaging==24.1
- pluggy==1.4.0
-pytest-env==1.1.3
- pytest==8.1.1
+
+pytest==8.3.3
+ iniconfig==2.0.0
+ packaging==24.1
+ pluggy==1.5.0
+pytest-cov==5.0.0
+ coverage==7.6.4
+ pytest==8.3.3
iniconfig==2.0.0
packaging==24.1
- pluggy==1.4.0
-pytest-profiling==1.7.0
- gprof2dot==2022.7.29
- pytest==8.1.1
+ pluggy==1.5.0
+pytest-env==1.1.5
+ pytest==8.3.3
iniconfig==2.0.0
packaging==24.1
- pluggy==1.4.0
- six==1.16.0
-pytest-rerunfailures==13.0
- packaging==24.1
- pytest==8.1.1
+ pluggy==1.5.0
+pytest-profiling==1.7.0
+ gprof2dot==2024.6.6
+ pytest==8.3.3
iniconfig==2.0.0
packaging==24.1
- pluggy==1.4.0
+ pluggy==1.5.0
+ six==1.16.0
+pytest-rerunfailures==14.0
+ packaging==24.1
+ pytest==8.3.3
+ iniconfig==2.0.0
+ packaging==24.1
+ pluggy==1.5.0
pytest-runner==6.0.1
pytest-sugar==1.0.0
packaging==24.1
- pytest==8.1.1
+ pytest==8.3.3
iniconfig==2.0.0
packaging==24.1
- pluggy==1.4.0
- termcolor==2.4.0
+ pluggy==1.5.0
+ termcolor==2.5.0
pytest-timeout==2.3.1
- pytest==8.1.1
+ pytest==8.3.3
iniconfig==2.0.0
packaging==24.1
- pluggy==1.4.0
-webtest==3.0.0
+ pluggy==1.5.0
+
+webtest==3.0.1
beautifulsoup4==4.12.3
soupsieve==2.5
- waitress==3.0.0
- webob==1.8.7
+ waitress==3.0.1
+ webob==1.8.9
# RhodeCode test-data
rc_testdata @ https://code.rhodecode.com/upstream/rc-testdata-dist/raw/77378e9097f700b4c1b9391b56199fe63566b5c9/rc_testdata-0.11.0.tar.gz#egg=rc_testdata
diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py
--- a/rhodecode/__init__.py
+++ b/rhodecode/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -45,13 +45,20 @@ CELERY_EAGER = False
# link to config for pyramid
CONFIG = {}
+class NotGivenMeta:
+
+ def __repr__(self):
+ return 'NotGivenObject()'
+ __str__ = __repr__
+
+NotGiven = NotGivenMeta()
class ConfigGet:
- NotGiven = object()
- def _get_val_or_missing(self, key, missing):
+ @classmethod
+ def _get_val_or_missing(cls, key, missing):
if key not in CONFIG:
- if missing == self.NotGiven:
+ if missing != NotGiven:
return missing
# we don't get key, we don't get missing value, return nothing similar as config.get(key)
return None
@@ -74,6 +81,12 @@ class ConfigGet:
val = self._get_val_or_missing(key, missing)
return str2bool(val)
+ def get_list(self, key, missing=NotGiven):
+ from rhodecode.lib.type_utils import aslist
+ val = self._get_val_or_missing(key, missing)
+ return aslist(val, sep=',')
+
+
# Populated with the settings dictionary from application init in
# rhodecode.conf.environment.load_pyramid_environment
PYRAMID_SETTINGS = {}
diff --git a/rhodecode/api/__init__.py b/rhodecode/api/__init__.py
--- a/rhodecode/api/__init__.py
+++ b/rhodecode/api/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/exc.py b/rhodecode/api/exc.py
--- a/rhodecode/api/exc.py
+++ b/rhodecode/api/exc.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/__init__.py b/rhodecode/api/tests/__init__.py
--- a/rhodecode/api/tests/__init__.py
+++ b/rhodecode/api/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/conftest.py b/rhodecode/api/tests/conftest.py
--- a/rhodecode/api/tests/conftest.py
+++ b/rhodecode/api/tests/conftest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,12 +21,12 @@ import pytest
from rhodecode.model.meta import Session
from rhodecode.model.user import UserModel
from rhodecode.model.auth_token import AuthTokenModel
-from rhodecode.tests import TEST_USER_ADMIN_LOGIN
@pytest.fixture(scope="class")
def testuser_api(request, baseapp):
cls = request.cls
+ from rhodecode.tests import TEST_USER_ADMIN_LOGIN
# ADMIN USER
cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
diff --git a/rhodecode/api/tests/test_add_field_to_repo.py b/rhodecode/api/tests/test_add_field_to_repo.py
--- a/rhodecode/api/tests/test_add_field_to_repo.py
+++ b/rhodecode/api/tests/test_add_field_to_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_add_user_to_user_group.py b/rhodecode/api/tests/test_add_user_to_user_group.py
--- a/rhodecode/api/tests/test_add_user_to_user_group.py
+++ b/rhodecode/api/tests/test_add_user_to_user_group.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_api.py b/rhodecode/api/tests/test_api.py
--- a/rhodecode/api/tests/test_api.py
+++ b/rhodecode/api/tests/test_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_cleanup_repos.py b/rhodecode/api/tests/test_cleanup_repos.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/api/tests/test_cleanup_repos.py
@@ -0,0 +1,44 @@
+# Copyright (C) 2010-2024 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# 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 Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import mock
+import pytest
+
+from rhodecode.model.scm import ScmModel
+from rhodecode.api.tests.utils import (
+ build_data, api_call, assert_ok, assert_error, crash)
+
+
+@pytest.mark.usefixtures("testuser_api", "app")
+class TestCleanupRepos(object):
+ def test_api_cleanup_repos(self):
+ id_, params = build_data(self.apikey, 'cleanup_repos')
+ response = api_call(self.app, params)
+
+ expected = {'removed': [], 'errors': []}
+ assert_ok(id_, expected, given=response.body)
+
+ def test_api_cleanup_repos_error(self):
+
+ id_, params = build_data(self.apikey, 'cleanup_repos', )
+
+ with mock.patch('rhodecode.lib.utils.repo2db_cleanup', side_effect=crash):
+ response = api_call(self.app, params)
+
+ expected = 'Error occurred during repo storage cleanup action'
+ assert_error(id_, expected, given=response.body)
diff --git a/rhodecode/api/tests/test_cleanup_sessions.py b/rhodecode/api/tests/test_cleanup_sessions.py
--- a/rhodecode/api/tests/test_cleanup_sessions.py
+++ b/rhodecode/api/tests/test_cleanup_sessions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_close_pull_request.py b/rhodecode/api/tests/test_close_pull_request.py
--- a/rhodecode/api/tests/test_close_pull_request.py
+++ b/rhodecode/api/tests/test_close_pull_request.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_comment_commit.py b/rhodecode/api/tests/test_comment_commit.py
--- a/rhodecode/api/tests/test_comment_commit.py
+++ b/rhodecode/api/tests/test_comment_commit.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_comment_pull_request.py b/rhodecode/api/tests/test_comment_pull_request.py
--- a/rhodecode/api/tests/test_comment_pull_request.py
+++ b/rhodecode/api/tests/test_comment_pull_request.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_create_gist.py b/rhodecode/api/tests/test_create_gist.py
--- a/rhodecode/api/tests/test_create_gist.py
+++ b/rhodecode/api/tests/test_create_gist.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -24,7 +23,7 @@ from rhodecode.model.db import Gist
from rhodecode.model.gist import GistModel
from rhodecode.api.tests.utils import (
build_data, api_call, assert_error, assert_ok, crash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
@pytest.mark.usefixtures("testuser_api", "app")
diff --git a/rhodecode/api/tests/test_create_pull_request.py b/rhodecode/api/tests/test_create_pull_request.py
--- a/rhodecode/api/tests/test_create_pull_request.py
+++ b/rhodecode/api/tests/test_create_pull_request.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_create_repo.py b/rhodecode/api/tests/test_create_repo.py
--- a/rhodecode/api/tests/test_create_repo.py
+++ b/rhodecode/api/tests/test_create_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -27,7 +26,7 @@ from rhodecode.model.user import UserMod
from rhodecode.tests import TEST_USER_ADMIN_LOGIN
from rhodecode.api.tests.utils import (
build_data, api_call, assert_ok, assert_error, crash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.lib.ext_json import json
from rhodecode.lib.str_utils import safe_str
diff --git a/rhodecode/api/tests/test_create_repo_group.py b/rhodecode/api/tests/test_create_repo_group.py
--- a/rhodecode/api/tests/test_create_repo_group.py
+++ b/rhodecode/api/tests/test_create_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -26,7 +25,7 @@ from rhodecode.model.user import UserMod
from rhodecode.tests import TEST_USER_ADMIN_LOGIN
from rhodecode.api.tests.utils import (
build_data, api_call, assert_ok, assert_error, crash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
fixture = Fixture()
diff --git a/rhodecode/api/tests/test_create_user.py b/rhodecode/api/tests/test_create_user.py
--- a/rhodecode/api/tests/test_create_user.py
+++ b/rhodecode/api/tests/test_create_user.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -26,7 +25,7 @@ from rhodecode.tests import (
TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
from rhodecode.api.tests.utils import (
build_data, api_call, assert_ok, assert_error, jsonify, crash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.model.db import RepoGroup
diff --git a/rhodecode/api/tests/test_create_user_group.py b/rhodecode/api/tests/test_create_user_group.py
--- a/rhodecode/api/tests/test_create_user_group.py
+++ b/rhodecode/api/tests/test_create_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -25,7 +24,7 @@ from rhodecode.model.user import UserMod
from rhodecode.model.user_group import UserGroupModel
from rhodecode.api.tests.utils import (
build_data, api_call, assert_error, assert_ok, crash, jsonify)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
@pytest.mark.usefixtures("testuser_api", "app")
diff --git a/rhodecode/api/tests/test_delete_gist.py b/rhodecode/api/tests/test_delete_gist.py
--- a/rhodecode/api/tests/test_delete_gist.py
+++ b/rhodecode/api/tests/test_delete_gist.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_delete_repo.py b/rhodecode/api/tests/test_delete_repo.py
--- a/rhodecode/api/tests/test_delete_repo.py
+++ b/rhodecode/api/tests/test_delete_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_delete_repo_group.py b/rhodecode/api/tests/test_delete_repo_group.py
--- a/rhodecode/api/tests/test_delete_repo_group.py
+++ b/rhodecode/api/tests/test_delete_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_delete_user.py b/rhodecode/api/tests/test_delete_user.py
--- a/rhodecode/api/tests/test_delete_user.py
+++ b/rhodecode/api/tests/test_delete_user.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_delete_user_group.py b/rhodecode/api/tests/test_delete_user_group.py
--- a/rhodecode/api/tests/test_delete_user_group.py
+++ b/rhodecode/api/tests/test_delete_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_deprecated_api.py b/rhodecode/api/tests/test_deprecated_api.py
--- a/rhodecode/api/tests/test_deprecated_api.py
+++ b/rhodecode/api/tests/test_deprecated_api.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_fork_repo.py b/rhodecode/api/tests/test_fork_repo.py
--- a/rhodecode/api/tests/test_fork_repo.py
+++ b/rhodecode/api/tests/test_fork_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -28,7 +27,7 @@ from rhodecode.model.user import UserMod
from rhodecode.tests import TEST_USER_ADMIN_LOGIN
from rhodecode.api.tests.utils import (
build_data, api_call, assert_error, assert_ok, crash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
fixture = Fixture()
diff --git a/rhodecode/api/tests/test_fts_search.py b/rhodecode/api/tests/test_fts_search.py
--- a/rhodecode/api/tests/test_fts_search.py
+++ b/rhodecode/api/tests/test_fts_search.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_gist.py b/rhodecode/api/tests/test_get_gist.py
--- a/rhodecode/api/tests/test_get_gist.py
+++ b/rhodecode/api/tests/test_get_gist.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_gists.py b/rhodecode/api/tests/test_get_gists.py
--- a/rhodecode/api/tests/test_get_gists.py
+++ b/rhodecode/api/tests/test_get_gists.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_ip.py b/rhodecode/api/tests/test_get_ip.py
--- a/rhodecode/api/tests/test_get_ip.py
+++ b/rhodecode/api/tests/test_get_ip.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_locks.py b/rhodecode/api/tests/test_get_locks.py
--- a/rhodecode/api/tests/test_get_locks.py
+++ b/rhodecode/api/tests/test_get_locks.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_method.py b/rhodecode/api/tests/test_get_method.py
--- a/rhodecode/api/tests/test_get_method.py
+++ b/rhodecode/api/tests/test_get_method.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_pull_request.py b/rhodecode/api/tests/test_get_pull_request.py
--- a/rhodecode/api/tests/test_get_pull_request.py
+++ b/rhodecode/api/tests/test_get_pull_request.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_pull_request_comments.py b/rhodecode/api/tests/test_get_pull_request_comments.py
--- a/rhodecode/api/tests/test_get_pull_request_comments.py
+++ b/rhodecode/api/tests/test_get_pull_request_comments.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_pull_requests.py b/rhodecode/api/tests/test_get_pull_requests.py
--- a/rhodecode/api/tests/test_get_pull_requests.py
+++ b/rhodecode/api/tests/test_get_pull_requests.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_repo.py b/rhodecode/api/tests/test_get_repo.py
--- a/rhodecode/api/tests/test_get_repo.py
+++ b/rhodecode/api/tests/test_get_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_repo_changeset.py b/rhodecode/api/tests/test_get_repo_changeset.py
--- a/rhodecode/api/tests/test_get_repo_changeset.py
+++ b/rhodecode/api/tests/test_get_repo_changeset.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_repo_comments.py b/rhodecode/api/tests/test_get_repo_comments.py
--- a/rhodecode/api/tests/test_get_repo_comments.py
+++ b/rhodecode/api/tests/test_get_repo_comments.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,6 +19,7 @@
import pytest
+from rhodecode.lib.str_utils import safe_str
from rhodecode.model.db import User, ChangesetComment
from rhodecode.model.meta import Session
from rhodecode.model.comment import CommentsModel
@@ -37,7 +37,7 @@ def make_repo_comments_factory(request):
commit = repo.scm_instance()[0]
commit_id = commit.raw_id
- file_0 = commit.affected_files[0]
+ file_0 = safe_str(commit.affected_files[0])
comments = []
# general
diff --git a/rhodecode/api/tests/test_get_repo_group.py b/rhodecode/api/tests/test_get_repo_group.py
--- a/rhodecode/api/tests/test_get_repo_group.py
+++ b/rhodecode/api/tests/test_get_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_repo_groups.py b/rhodecode/api/tests/test_get_repo_groups.py
--- a/rhodecode/api/tests/test_get_repo_groups.py
+++ b/rhodecode/api/tests/test_get_repo_groups.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_repo_nodes.py b/rhodecode/api/tests/test_get_repo_nodes.py
--- a/rhodecode/api/tests/test_get_repo_nodes.py
+++ b/rhodecode/api/tests/test_get_repo_nodes.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_repo_refs.py b/rhodecode/api/tests/test_get_repo_refs.py
--- a/rhodecode/api/tests/test_get_repo_refs.py
+++ b/rhodecode/api/tests/test_get_repo_refs.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_repos.py b/rhodecode/api/tests/test_get_repos.py
--- a/rhodecode/api/tests/test_get_repos.py
+++ b/rhodecode/api/tests/test_get_repos.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_server_info.py b/rhodecode/api/tests/test_get_server_info.py
--- a/rhodecode/api/tests/test_get_server_info.py
+++ b/rhodecode/api/tests/test_get_server_info.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_user.py b/rhodecode/api/tests/test_get_user.py
--- a/rhodecode/api/tests/test_get_user.py
+++ b/rhodecode/api/tests/test_get_user.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_user_group.py b/rhodecode/api/tests/test_get_user_group.py
--- a/rhodecode/api/tests/test_get_user_group.py
+++ b/rhodecode/api/tests/test_get_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_user_groups.py b/rhodecode/api/tests/test_get_user_groups.py
--- a/rhodecode/api/tests/test_get_user_groups.py
+++ b/rhodecode/api/tests/test_get_user_groups.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_get_users.py b/rhodecode/api/tests/test_get_users.py
--- a/rhodecode/api/tests/test_get_users.py
+++ b/rhodecode/api/tests/test_get_users.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_grant_user_group_permission.py b/rhodecode/api/tests/test_grant_user_group_permission.py
--- a/rhodecode/api/tests/test_grant_user_group_permission.py
+++ b/rhodecode/api/tests/test_grant_user_group_permission.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_grant_user_group_permission_to_repo_group.py b/rhodecode/api/tests/test_grant_user_group_permission_to_repo_group.py
--- a/rhodecode/api/tests/test_grant_user_group_permission_to_repo_group.py
+++ b/rhodecode/api/tests/test_grant_user_group_permission_to_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_grant_user_group_permission_to_user_group.py b/rhodecode/api/tests/test_grant_user_group_permission_to_user_group.py
--- a/rhodecode/api/tests/test_grant_user_group_permission_to_user_group.py
+++ b/rhodecode/api/tests/test_grant_user_group_permission_to_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_grant_user_permission.py b/rhodecode/api/tests/test_grant_user_permission.py
--- a/rhodecode/api/tests/test_grant_user_permission.py
+++ b/rhodecode/api/tests/test_grant_user_permission.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_grant_user_permission_to_repo_group.py b/rhodecode/api/tests/test_grant_user_permission_to_repo_group.py
--- a/rhodecode/api/tests/test_grant_user_permission_to_repo_group.py
+++ b/rhodecode/api/tests/test_grant_user_permission_to_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_grant_user_permission_to_user_group.py b/rhodecode/api/tests/test_grant_user_permission_to_user_group.py
--- a/rhodecode/api/tests/test_grant_user_permission_to_user_group.py
+++ b/rhodecode/api/tests/test_grant_user_permission_to_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_invalidate_cache.py b/rhodecode/api/tests/test_invalidate_cache.py
--- a/rhodecode/api/tests/test_invalidate_cache.py
+++ b/rhodecode/api/tests/test_invalidate_cache.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_merge_pull_request.py b/rhodecode/api/tests/test_merge_pull_request.py
--- a/rhodecode/api/tests/test_merge_pull_request.py
+++ b/rhodecode/api/tests/test_merge_pull_request.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_pull.py b/rhodecode/api/tests/test_pull.py
--- a/rhodecode/api/tests/test_pull.py
+++ b/rhodecode/api/tests/test_pull.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_remove_field_from_repo.py b/rhodecode/api/tests/test_remove_field_from_repo.py
--- a/rhodecode/api/tests/test_remove_field_from_repo.py
+++ b/rhodecode/api/tests/test_remove_field_from_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_remove_user_from_user_group.py b/rhodecode/api/tests/test_remove_user_from_user_group.py
--- a/rhodecode/api/tests/test_remove_user_from_user_group.py
+++ b/rhodecode/api/tests/test_remove_user_from_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_repo_locking.py b/rhodecode/api/tests/test_repo_locking.py
--- a/rhodecode/api/tests/test_repo_locking.py
+++ b/rhodecode/api/tests/test_repo_locking.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_rescan_repos.py b/rhodecode/api/tests/test_rescan_repos.py
--- a/rhodecode/api/tests/test_rescan_repos.py
+++ b/rhodecode/api/tests/test_rescan_repos.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,7 +19,6 @@
import mock
import pytest
-from rhodecode.model.scm import ScmModel
from rhodecode.api.tests.utils import (
build_data, api_call, assert_ok, assert_error, crash)
@@ -31,13 +29,14 @@ class TestRescanRepos(object):
id_, params = build_data(self.apikey, 'rescan_repos')
response = api_call(self.app, params)
- expected = {'added': [], 'removed': []}
+ expected = {'added': [], 'errors': []}
assert_ok(id_, expected, given=response.body)
- @mock.patch.object(ScmModel, 'repo_scan', crash)
- def test_api_rescann_error(self):
+ def test_api_rescan_repos_error(self):
id_, params = build_data(self.apikey, 'rescan_repos', )
- response = api_call(self.app, params)
+
+ with mock.patch('rhodecode.lib.utils.repo2db_mapper', side_effect=crash):
+ response = api_call(self.app, params)
expected = 'Error occurred during rescan repositories action'
assert_error(id_, expected, given=response.body)
diff --git a/rhodecode/api/tests/test_revoke_user_group_permission.py b/rhodecode/api/tests/test_revoke_user_group_permission.py
--- a/rhodecode/api/tests/test_revoke_user_group_permission.py
+++ b/rhodecode/api/tests/test_revoke_user_group_permission.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_revoke_user_group_permission_from_repo_group.py b/rhodecode/api/tests/test_revoke_user_group_permission_from_repo_group.py
--- a/rhodecode/api/tests/test_revoke_user_group_permission_from_repo_group.py
+++ b/rhodecode/api/tests/test_revoke_user_group_permission_from_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_revoke_user_group_permission_from_user_group.py b/rhodecode/api/tests/test_revoke_user_group_permission_from_user_group.py
--- a/rhodecode/api/tests/test_revoke_user_group_permission_from_user_group.py
+++ b/rhodecode/api/tests/test_revoke_user_group_permission_from_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_revoke_user_permission.py b/rhodecode/api/tests/test_revoke_user_permission.py
--- a/rhodecode/api/tests/test_revoke_user_permission.py
+++ b/rhodecode/api/tests/test_revoke_user_permission.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_revoke_user_permission_from_repo_group.py b/rhodecode/api/tests/test_revoke_user_permission_from_repo_group.py
--- a/rhodecode/api/tests/test_revoke_user_permission_from_repo_group.py
+++ b/rhodecode/api/tests/test_revoke_user_permission_from_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_revoke_user_permission_from_user_group.py b/rhodecode/api/tests/test_revoke_user_permission_from_user_group.py
--- a/rhodecode/api/tests/test_revoke_user_permission_from_user_group.py
+++ b/rhodecode/api/tests/test_revoke_user_permission_from_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_service_api.py b/rhodecode/api/tests/test_service_api.py
--- a/rhodecode/api/tests/test_service_api.py
+++ b/rhodecode/api/tests/test_service_api.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_store_exception.py b/rhodecode/api/tests/test_store_exception.py
--- a/rhodecode/api/tests/test_store_exception.py
+++ b/rhodecode/api/tests/test_store_exception.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_update_pull_request.py b/rhodecode/api/tests/test_update_pull_request.py
--- a/rhodecode/api/tests/test_update_pull_request.py
+++ b/rhodecode/api/tests/test_update_pull_request.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_update_repo.py b/rhodecode/api/tests/test_update_repo.py
--- a/rhodecode/api/tests/test_update_repo.py
+++ b/rhodecode/api/tests/test_update_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -25,8 +24,8 @@ from rhodecode.model.scm import ScmModel
from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
from rhodecode.api.tests.utils import (
build_data, api_call, assert_error, assert_ok, crash, jsonify)
-from rhodecode.tests.fixture import Fixture
-from rhodecode.tests.fixture_mods.fixture_utils import plain_http_host_only_stub
+from rhodecode.tests.fixtures.rc_fixture import Fixture
+from rhodecode.tests.fixtures.fixture_utils import plain_http_host_only_stub
fixture = Fixture()
diff --git a/rhodecode/api/tests/test_update_repo_group.py b/rhodecode/api/tests/test_update_repo_group.py
--- a/rhodecode/api/tests/test_update_repo_group.py
+++ b/rhodecode/api/tests/test_update_repo_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_update_user.py b/rhodecode/api/tests/test_update_user.py
--- a/rhodecode/api/tests/test_update_user.py
+++ b/rhodecode/api/tests/test_update_user.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_update_user_group.py b/rhodecode/api/tests/test_update_user_group.py
--- a/rhodecode/api/tests/test_update_user_group.py
+++ b/rhodecode/api/tests/test_update_user_group.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/test_utils.py b/rhodecode/api/tests/test_utils.py
--- a/rhodecode/api/tests/test_utils.py
+++ b/rhodecode/api/tests/test_utils.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/tests/utils.py b/rhodecode/api/tests/utils.py
--- a/rhodecode/api/tests/utils.py
+++ b/rhodecode/api/tests/utils.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/utils.py b/rhodecode/api/utils.py
--- a/rhodecode/api/utils.py
+++ b/rhodecode/api/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/__init__.py b/rhodecode/api/views/__init__.py
--- a/rhodecode/api/views/__init__.py
+++ b/rhodecode/api/views/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2023 RhodeCode GmbH
+# Copyright (C) 2015-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/deprecated_api.py b/rhodecode/api/views/deprecated_api.py
--- a/rhodecode/api/views/deprecated_api.py
+++ b/rhodecode/api/views/deprecated_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/gist_api.py b/rhodecode/api/views/gist_api.py
--- a/rhodecode/api/views/gist_api.py
+++ b/rhodecode/api/views/gist_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/pull_request_api.py b/rhodecode/api/views/pull_request_api.py
--- a/rhodecode/api/views/pull_request_api.py
+++ b/rhodecode/api/views/pull_request_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/repo_api.py b/rhodecode/api/views/repo_api.py
--- a/rhodecode/api/views/repo_api.py
+++ b/rhodecode/api/views/repo_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -317,8 +317,7 @@ def get_repo_changeset(request, apiuser,
','.join(_changes_details_types)))
vcs_repo = repo.scm_instance()
- pre_load = ['author', 'branch', 'date', 'message', 'parents',
- 'status', '_commit', '_file_paths']
+ pre_load = ['author', 'branch', 'date', 'message', 'parents', 'status', '_commit']
try:
commit = repo.get_commit(commit_id=revision, pre_load=pre_load)
@@ -376,8 +375,7 @@ def get_repo_changesets(request, apiuser
','.join(_changes_details_types)))
limit = int(limit)
- pre_load = ['author', 'branch', 'date', 'message', 'parents',
- 'status', '_commit', '_file_paths']
+ pre_load = ['author', 'branch', 'date', 'message', 'parents', 'status', '_commit']
vcs_repo = repo.scm_instance()
# SVN needs a special case to distinguish its index and commit id
diff --git a/rhodecode/api/views/repo_group_api.py b/rhodecode/api/views/repo_group_api.py
--- a/rhodecode/api/views/repo_group_api.py
+++ b/rhodecode/api/views/repo_group_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/search_api.py b/rhodecode/api/views/search_api.py
--- a/rhodecode/api/views/search_api.py
+++ b/rhodecode/api/views/search_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/server_api.py b/rhodecode/api/views/server_api.py
--- a/rhodecode/api/views/server_api.py
+++ b/rhodecode/api/views/server_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -18,14 +18,13 @@
import logging
import itertools
-import base64
from rhodecode.api import (
jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
from rhodecode.api.utils import (
Optional, OAttr, has_superadmin_permission, get_user_or_error)
-from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
+from rhodecode.lib.utils import get_rhodecode_repo_store_path
from rhodecode.lib import system_info
from rhodecode.lib import user_sessions
from rhodecode.lib import exc_tracking
@@ -33,9 +32,7 @@ from rhodecode.lib.ext_json import json
from rhodecode.lib.utils2 import safe_int
from rhodecode.model.db import UserIpMap
from rhodecode.model.scm import ScmModel
-from rhodecode.apps.file_store import utils as store_utils
-from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
- FileOverSizeException
+
log = logging.getLogger(__name__)
@@ -158,13 +155,10 @@ def get_ip(request, apiuser, userid=Opti
@jsonrpc_method()
-def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
+def rescan_repos(request, apiuser):
"""
Triggers a rescan of the specified repositories.
-
- * If the ``remove_obsolete`` option is set, it also deletes repositories
- that are found in the database but not on the file system, so called
- "clean zombies".
+ It returns list of added repositories, and errors during scan.
This command can only be run using an |authtoken| with admin rights to
the specified repository.
@@ -173,9 +167,6 @@ def rescan_repos(request, apiuser, remov
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
- :param remove_obsolete: Deletes repositories from the database that
- are not found on the filesystem.
- :type remove_obsolete: Optional(``True`` | ``False``)
Example output:
@@ -184,7 +175,7 @@ def rescan_repos(request, apiuser, remov
id :
result : {
'added': [,...]
- 'removed': [,...]
+ 'errors': [,...]
}
error : null
@@ -199,20 +190,69 @@ def rescan_repos(request, apiuser, remov
}
"""
+ from rhodecode.lib.utils import repo2db_mapper # re-import for testing patches
+
if not has_superadmin_permission(apiuser):
raise JSONRPCForbidden()
try:
- rm_obsolete = Optional.extract(remove_obsolete)
- added, removed = repo2db_mapper(ScmModel().repo_scan(),
- remove_obsolete=rm_obsolete, force_hooks_rebuild=True)
- return {'added': added, 'removed': removed}
+ added, errors = repo2db_mapper(ScmModel().repo_scan(), force_hooks_rebuild=True)
+ return {'added': added, 'errors': errors}
except Exception:
- log.exception('Failed to run repo rescann')
+ log.exception('Failed to run repo rescan')
raise JSONRPCError(
'Error occurred during rescan repositories action'
)
+@jsonrpc_method()
+def cleanup_repos(request, apiuser):
+ """
+ Triggers a cleanup of non-existing repositories or repository groups in filesystem.
+
+ This command can only be run using an |authtoken| with admin rights to
+ the specified repository.
+
+ This command takes the following options:
+
+ :param apiuser: This is filled automatically from the |authtoken|.
+ :type apiuser: AuthUser
+
+ Example output:
+
+ .. code-block:: bash
+
+ id :
+ result : {
+ 'removed': [,...]
+ 'errors': [,...]
+ }
+ error : null
+
+ Example error output:
+
+ .. code-block:: bash
+
+ id :
+ result : null
+ error : {
+ 'Error occurred during repo storage cleanup action'
+ }
+
+ """
+ from rhodecode.lib.utils import repo2db_cleanup # re-import for testing patches
+
+ if not has_superadmin_permission(apiuser):
+ raise JSONRPCForbidden()
+
+ try:
+ removed, errors = repo2db_cleanup()
+ return {'removed': removed, 'errors': errors}
+ except Exception:
+ log.exception('Failed to run repo storage cleanup')
+ raise JSONRPCError(
+ 'Error occurred during repo storage cleanup action'
+ )
+
@jsonrpc_method()
def cleanup_sessions(request, apiuser, older_then=Optional(60)):
diff --git a/rhodecode/api/views/service_api.py b/rhodecode/api/views/service_api.py
--- a/rhodecode/api/views/service_api.py
+++ b/rhodecode/api/views/service_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/testing_api.py b/rhodecode/api/views/testing_api.py
--- a/rhodecode/api/views/testing_api.py
+++ b/rhodecode/api/views/testing_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/user_api.py b/rhodecode/api/views/user_api.py
--- a/rhodecode/api/views/user_api.py
+++ b/rhodecode/api/views/user_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/api/views/user_group_api.py b/rhodecode/api/views/user_group_api.py
--- a/rhodecode/api/views/user_group_api.py
+++ b/rhodecode/api/views/user_group_api.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/__init__.py b/rhodecode/apps/__init__.py
--- a/rhodecode/apps/__init__.py
+++ b/rhodecode/apps/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py
--- a/rhodecode/apps/_base/__init__.py
+++ b/rhodecode/apps/_base/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -395,7 +395,7 @@ class RepoAppView(BaseAppView):
settings = settings_model.get_repo_settings_inherited()
return settings.get(settings_key, default)
- def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path="/"):
+ def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path="/", nodes=None):
log.debug("Looking for README file at path %s", path)
if commit_id:
landing_commit_id = commit_id
@@ -413,16 +413,14 @@ class RepoAppView(BaseAppView):
@region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
def generate_repo_readme(
- repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type
+ _repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type
):
- readme_data = None
- readme_filename = None
+ _readme_data = None
+ _readme_filename = None
commit = db_repo.get_commit(_commit_id)
log.debug("Searching for a README file at commit %s.", _commit_id)
- readme_node = ReadmeFinder(_renderer_type).search(
- commit, path=_readme_search_path
- )
+ readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path, nodes=nodes)
if readme_node:
log.debug("Found README node: %s", readme_node)
@@ -442,19 +440,19 @@ class RepoAppView(BaseAppView):
),
}
- readme_data = self._render_readme_or_none(
+ _readme_data = self._render_readme_or_none(
commit, readme_node, relative_urls
)
- readme_filename = readme_node.str_path
+ _readme_filename = readme_node.str_path
- return readme_data, readme_filename
+ return _readme_data, _readme_filename
readme_data, readme_filename = generate_repo_readme(
db_repo.repo_id,
landing_commit_id,
db_repo.repo_name,
path,
- renderer_type,
+ renderer_type
)
compute_time = time.time() - start
diff --git a/rhodecode/apps/_base/interfaces.py b/rhodecode/apps/_base/interfaces.py
--- a/rhodecode/apps/_base/interfaces.py
+++ b/rhodecode/apps/_base/interfaces.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/_base/navigation.py b/rhodecode/apps/_base/navigation.py
--- a/rhodecode/apps/_base/navigation.py
+++ b/rhodecode/apps/_base/navigation.py
@@ -1,6 +1,5 @@
-import dataclasses
-# Copyright (C) 2016-2023 RhodeCode GmbH
+import dataclasses# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/_base/subscribers.py b/rhodecode/apps/_base/subscribers.py
--- a/rhodecode/apps/_base/subscribers.py
+++ b/rhodecode/apps/_base/subscribers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/__init__.py b/rhodecode/apps/admin/__init__.py
--- a/rhodecode/apps/admin/__init__.py
+++ b/rhodecode/apps/admin/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -361,12 +361,21 @@ def admin_routes(config):
renderer='rhodecode:templates/admin/settings/settings.mako')
config.add_route(
- name='admin_settings_mapping_update',
- pattern='/settings/mapping/update')
+ name='admin_settings_mapping_create',
+ pattern='/settings/mapping/create')
config.add_view(
AdminSettingsView,
- attr='settings_mapping_update',
- route_name='admin_settings_mapping_update', request_method='POST',
+ attr='settings_mapping_create',
+ route_name='admin_settings_mapping_create', request_method='POST',
+ renderer='rhodecode:templates/admin/settings/settings.mako')
+
+ config.add_route(
+ name='admin_settings_mapping_cleanup',
+ pattern='/settings/mapping/cleanup')
+ config.add_view(
+ AdminSettingsView,
+ attr='settings_mapping_cleanup',
+ route_name='admin_settings_mapping_cleanup', request_method='POST',
renderer='rhodecode:templates/admin/settings/settings.mako')
config.add_route(
diff --git a/rhodecode/apps/admin/tests/test_admin_audit_logs.py b/rhodecode/apps/admin/tests/test_admin_audit_logs.py
--- a/rhodecode/apps/admin/tests/test_admin_audit_logs.py
+++ b/rhodecode/apps/admin/tests/test_admin_audit_logs.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -26,7 +25,7 @@ import pytest
from rhodecode.lib.str_utils import safe_str
from rhodecode.tests import *
from rhodecode.tests.routes import route_path
-from rhodecode.tests.fixture import FIXTURES
+from rhodecode.tests.fixtures.rc_fixture import FIXTURES
from rhodecode.model.db import UserLog
from rhodecode.model.meta import Session
diff --git a/rhodecode/apps/admin/tests/test_admin_auth_settings.py b/rhodecode/apps/admin/tests/test_admin_auth_settings.py
--- a/rhodecode/apps/admin/tests/test_admin_auth_settings.py
+++ b/rhodecode/apps/admin/tests/test_admin_auth_settings.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/tests/test_admin_defaults.py b/rhodecode/apps/admin/tests/test_admin_defaults.py
--- a/rhodecode/apps/admin/tests/test_admin_defaults.py
+++ b/rhodecode/apps/admin/tests/test_admin_defaults.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/tests/test_admin_main_views.py b/rhodecode/apps/admin/tests/test_admin_main_views.py
--- a/rhodecode/apps/admin/tests/test_admin_main_views.py
+++ b/rhodecode/apps/admin/tests/test_admin_main_views.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,7 +19,7 @@
import pytest
from rhodecode.tests import TestController
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/admin/tests/test_admin_permissions.py b/rhodecode/apps/admin/tests/test_admin_permissions.py
--- a/rhodecode/apps/admin/tests/test_admin_permissions.py
+++ b/rhodecode/apps/admin/tests/test_admin_permissions.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/tests/test_admin_repos.py b/rhodecode/apps/admin/tests/test_admin_repos.py
--- a/rhodecode/apps/admin/tests/test_admin_repos.py
+++ b/rhodecode/apps/admin/tests/test_admin_repos.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -37,7 +36,7 @@ from rhodecode.model.user import UserMod
from rhodecode.tests import (
login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN,
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-from rhodecode.tests.fixture import Fixture, error_function
+from rhodecode.tests.fixtures.rc_fixture import Fixture, error_function
from rhodecode.tests.utils import repo_on_filesystem
from rhodecode.tests.routes import route_path
@@ -111,9 +110,11 @@ class TestAdminRepos(object):
repo_type=backend.alias,
repo_description=description,
csrf_token=csrf_token))
-
- self.assert_repository_is_created_correctly(
- repo_name, description, backend)
+ try:
+ self.assert_repository_is_created_correctly(repo_name, description, backend)
+ finally:
+ RepoModel().delete(numeric_repo)
+ Session().commit()
@pytest.mark.parametrize("suffix", ['', '_ąćę'], ids=['', 'non-ascii'])
def test_create_in_group(
diff --git a/rhodecode/apps/admin/tests/test_admin_repository_groups.py b/rhodecode/apps/admin/tests/test_admin_repository_groups.py
--- a/rhodecode/apps/admin/tests/test_admin_repository_groups.py
+++ b/rhodecode/apps/admin/tests/test_admin_repository_groups.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -27,7 +26,7 @@ from rhodecode.model.meta import Session
from rhodecode.model.repo_group import RepoGroupModel
from rhodecode.tests import (
assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/admin/tests/test_admin_settings.py b/rhodecode/apps/admin/tests/test_admin_settings.py
--- a/rhodecode/apps/admin/tests/test_admin_settings.py
+++ b/rhodecode/apps/admin/tests/test_admin_settings.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/tests/test_admin_user_groups.py b/rhodecode/apps/admin/tests/test_admin_user_groups.py
--- a/rhodecode/apps/admin/tests/test_admin_user_groups.py
+++ b/rhodecode/apps/admin/tests/test_admin_user_groups.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -24,7 +23,7 @@ from rhodecode.model.meta import Session
from rhodecode.tests import (
TestController, assert_session_flash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/admin/tests/test_admin_users.py b/rhodecode/apps/admin/tests/test_admin_users.py
--- a/rhodecode/apps/admin/tests/test_admin_users.py
+++ b/rhodecode/apps/admin/tests/test_admin_users.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -28,7 +27,7 @@ from rhodecode.model.user import UserMod
from rhodecode.tests import (
TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py b/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py
--- a/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py
+++ b/rhodecode/apps/admin/tests/test_admin_users_ssh_keys.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,7 +21,7 @@ import pytest
from rhodecode.model.db import User, UserSshKeys
from rhodecode.tests import TestController, assert_session_flash
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/admin/views/__init__.py b/rhodecode/apps/admin/views/__init__.py
--- a/rhodecode/apps/admin/views/__init__.py
+++ b/rhodecode/apps/admin/views/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/artifacts.py b/rhodecode/apps/admin/views/artifacts.py
--- a/rhodecode/apps/admin/views/artifacts.py
+++ b/rhodecode/apps/admin/views/artifacts.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/audit_logs.py b/rhodecode/apps/admin/views/audit_logs.py
--- a/rhodecode/apps/admin/views/audit_logs.py
+++ b/rhodecode/apps/admin/views/audit_logs.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/automation.py b/rhodecode/apps/admin/views/automation.py
--- a/rhodecode/apps/admin/views/automation.py
+++ b/rhodecode/apps/admin/views/automation.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/defaults.py b/rhodecode/apps/admin/views/defaults.py
--- a/rhodecode/apps/admin/views/defaults.py
+++ b/rhodecode/apps/admin/views/defaults.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/exception_tracker.py b/rhodecode/apps/admin/views/exception_tracker.py
--- a/rhodecode/apps/admin/views/exception_tracker.py
+++ b/rhodecode/apps/admin/views/exception_tracker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2023 RhodeCode GmbH
+# Copyright (C) 2018-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/main_views.py b/rhodecode/apps/admin/views/main_views.py
--- a/rhodecode/apps/admin/views/main_views.py
+++ b/rhodecode/apps/admin/views/main_views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/open_source_licenses.py b/rhodecode/apps/admin/views/open_source_licenses.py
--- a/rhodecode/apps/admin/views/open_source_licenses.py
+++ b/rhodecode/apps/admin/views/open_source_licenses.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/permissions.py b/rhodecode/apps/admin/views/permissions.py
--- a/rhodecode/apps/admin/views/permissions.py
+++ b/rhodecode/apps/admin/views/permissions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/process_management.py b/rhodecode/apps/admin/views/process_management.py
--- a/rhodecode/apps/admin/views/process_management.py
+++ b/rhodecode/apps/admin/views/process_management.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/repo_groups.py b/rhodecode/apps/admin/views/repo_groups.py
--- a/rhodecode/apps/admin/views/repo_groups.py
+++ b/rhodecode/apps/admin/views/repo_groups.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/repositories.py b/rhodecode/apps/admin/views/repositories.py
--- a/rhodecode/apps/admin/views/repositories.py
+++ b/rhodecode/apps/admin/views/repositories.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/scheduler.py b/rhodecode/apps/admin/views/scheduler.py
--- a/rhodecode/apps/admin/views/scheduler.py
+++ b/rhodecode/apps/admin/views/scheduler.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/sessions.py b/rhodecode/apps/admin/views/sessions.py
--- a/rhodecode/apps/admin/views/sessions.py
+++ b/rhodecode/apps/admin/views/sessions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/settings.py b/rhodecode/apps/admin/views/settings.py
--- a/rhodecode/apps/admin/views/settings.py
+++ b/rhodecode/apps/admin/views/settings.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -38,7 +38,7 @@ from rhodecode.lib.auth import (
LoginRequired, HasPermissionAllDecorator, CSRFRequired)
from rhodecode.lib.celerylib import tasks, run_task
from rhodecode.lib.str_utils import safe_str
-from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
+from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path, repo2db_cleanup
from rhodecode.lib.utils2 import str2bool, AttributeDict
from rhodecode.lib.index import searcher_from_config
@@ -77,13 +77,16 @@ class AdminSettingsView(BaseAppView):
raise Exception('Could not get application ui settings !')
settings = {
# legacy param that needs to be kept
- 'web_push_ssl': False
+ 'web_push_ssl': False,
+ 'extensions_hgsubversion': False
}
for each in ret:
k = each.ui_key
v = each.ui_value
+ section = each.ui_section
+
# skip some options if they are defined
- if k in ['push_ssl']:
+ if f"{section}_{k}" in ['web_push_ssl', 'extensions_hgsubversion']:
continue
if k == '/':
@@ -98,7 +101,7 @@ class AdminSettingsView(BaseAppView):
if each.ui_section in ['hooks', 'extensions']:
v = each.ui_active
- settings[each.ui_section + '_' + k] = v
+ settings[section + '_' + k] = v
return settings
@@ -125,8 +128,6 @@ class AdminSettingsView(BaseAppView):
c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
defaults = self._form_defaults()
- model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
-
data = render('rhodecode:templates/admin/settings/settings.mako',
self._get_template_context(c), self.request)
html = formencode.htmlfill.render(
@@ -232,13 +233,12 @@ class AdminSettingsView(BaseAppView):
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
@CSRFRequired()
- def settings_mapping_update(self):
+ def settings_mapping_create(self):
_ = self.request.translate
c = self.load_default_context()
c.active = 'mapping'
- rm_obsolete = self.request.POST.get('destroy', False)
invalidate_cache = self.request.POST.get('invalidate', False)
- log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
+ log.debug('rescanning repo location')
if invalidate_cache:
log.debug('invalidating all repositories cache')
@@ -246,16 +246,34 @@ class AdminSettingsView(BaseAppView):
ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
filesystem_repos = ScmModel().repo_scan()
- added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, force_hooks_rebuild=True)
+ added, errors = repo2db_mapper(filesystem_repos, force_hooks_rebuild=True)
PermissionModel().trigger_permission_flush()
def _repr(rm_repo):
return ', '.join(map(safe_str, rm_repo)) or '-'
- h.flash(_('Repositories successfully '
- 'rescanned added: %s ; removed: %s') %
- (_repr(added), _repr(removed)),
- category='success')
+ if errors:
+ h.flash(_('Errors during scan: {}').format(_repr(errors), ), category='error')
+
+ h.flash(_('Repositories successfully scanned: Added: {}').format(_repr(added)), category='success')
+ raise HTTPFound(h.route_path('admin_settings_mapping'))
+
+ @LoginRequired()
+ @HasPermissionAllDecorator('hg.admin')
+ @CSRFRequired()
+ def settings_mapping_cleanup(self):
+ _ = self.request.translate
+ c = self.load_default_context()
+ c.active = 'mapping'
+ log.debug('rescanning repo location')
+
+ removed, errors = repo2db_cleanup()
+ PermissionModel().trigger_permission_flush()
+
+ def _repr(rm_repo):
+ return ', '.join(map(safe_str, rm_repo)) or '-'
+
+ h.flash(_('Repositories successfully scanned: Errors: {}, Added: {}').format(errors, _repr(removed)), category='success')
raise HTTPFound(h.route_path('admin_settings_mapping'))
@LoginRequired()
diff --git a/rhodecode/apps/admin/views/svn_config.py b/rhodecode/apps/admin/views/svn_config.py
--- a/rhodecode/apps/admin/views/svn_config.py
+++ b/rhodecode/apps/admin/views/svn_config.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/system_info.py b/rhodecode/apps/admin/views/system_info.py
--- a/rhodecode/apps/admin/views/system_info.py
+++ b/rhodecode/apps/admin/views/system_info.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/user_groups.py b/rhodecode/apps/admin/views/user_groups.py
--- a/rhodecode/apps/admin/views/user_groups.py
+++ b/rhodecode/apps/admin/views/user_groups.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/admin/views/users.py b/rhodecode/apps/admin/views/users.py
--- a/rhodecode/apps/admin/views/users.py
+++ b/rhodecode/apps/admin/views/users.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/channelstream/__init__.py b/rhodecode/apps/channelstream/__init__.py
--- a/rhodecode/apps/channelstream/__init__.py
+++ b/rhodecode/apps/channelstream/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/channelstream/views.py b/rhodecode/apps/channelstream/views.py
--- a/rhodecode/apps/channelstream/views.py
+++ b/rhodecode/apps/channelstream/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/debug_style/__init__.py b/rhodecode/apps/debug_style/__init__.py
--- a/rhodecode/apps/debug_style/__init__.py
+++ b/rhodecode/apps/debug_style/__init__.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/debug_style/views.py b/rhodecode/apps/debug_style/views.py
--- a/rhodecode/apps/debug_style/views.py
+++ b/rhodecode/apps/debug_style/views.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/__init__.py b/rhodecode/apps/file_store/__init__.py
--- a/rhodecode/apps/file_store/__init__.py
+++ b/rhodecode/apps/file_store/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/backends/__init__.py b/rhodecode/apps/file_store/backends/__init__.py
--- a/rhodecode/apps/file_store/backends/__init__.py
+++ b/rhodecode/apps/file_store/backends/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/backends/base.py b/rhodecode/apps/file_store/backends/base.py
--- a/rhodecode/apps/file_store/backends/base.py
+++ b/rhodecode/apps/file_store/backends/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/backends/filesystem.py b/rhodecode/apps/file_store/backends/filesystem.py
--- a/rhodecode/apps/file_store/backends/filesystem.py
+++ b/rhodecode/apps/file_store/backends/filesystem.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/backends/filesystem_legacy.py b/rhodecode/apps/file_store/backends/filesystem_legacy.py
--- a/rhodecode/apps/file_store/backends/filesystem_legacy.py
+++ b/rhodecode/apps/file_store/backends/filesystem_legacy.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/backends/objectstore.py b/rhodecode/apps/file_store/backends/objectstore.py
--- a/rhodecode/apps/file_store/backends/objectstore.py
+++ b/rhodecode/apps/file_store/backends/objectstore.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/config_keys.py b/rhodecode/apps/file_store/config_keys.py
--- a/rhodecode/apps/file_store/config_keys.py
+++ b/rhodecode/apps/file_store/config_keys.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/exceptions.py b/rhodecode/apps/file_store/exceptions.py
--- a/rhodecode/apps/file_store/exceptions.py
+++ b/rhodecode/apps/file_store/exceptions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/extensions.py b/rhodecode/apps/file_store/extensions.py
--- a/rhodecode/apps/file_store/extensions.py
+++ b/rhodecode/apps/file_store/extensions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/tests/__init__.py b/rhodecode/apps/file_store/tests/__init__.py
--- a/rhodecode/apps/file_store/tests/__init__.py
+++ b/rhodecode/apps/file_store/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/tests/test_filestore_backends.py b/rhodecode/apps/file_store/tests/test_filestore_backends.py
--- a/rhodecode/apps/file_store/tests/test_filestore_backends.py
+++ b/rhodecode/apps/file_store/tests/test_filestore_backends.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/tests/test_filestore_filesystem_backend.py b/rhodecode/apps/file_store/tests/test_filestore_filesystem_backend.py
--- a/rhodecode/apps/file_store/tests/test_filestore_filesystem_backend.py
+++ b/rhodecode/apps/file_store/tests/test_filestore_filesystem_backend.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/tests/test_filestore_legacy_and_v2_compatability.py b/rhodecode/apps/file_store/tests/test_filestore_legacy_and_v2_compatability.py
--- a/rhodecode/apps/file_store/tests/test_filestore_legacy_and_v2_compatability.py
+++ b/rhodecode/apps/file_store/tests/test_filestore_legacy_and_v2_compatability.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/tests/test_filestore_legacy_backend.py b/rhodecode/apps/file_store/tests/test_filestore_legacy_backend.py
--- a/rhodecode/apps/file_store/tests/test_filestore_legacy_backend.py
+++ b/rhodecode/apps/file_store/tests/test_filestore_legacy_backend.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/tests/test_filestore_objectstore_backend.py b/rhodecode/apps/file_store/tests/test_filestore_objectstore_backend.py
--- a/rhodecode/apps/file_store/tests/test_filestore_objectstore_backend.py
+++ b/rhodecode/apps/file_store/tests/test_filestore_objectstore_backend.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/tests/test_upload_file.py b/rhodecode/apps/file_store/tests/test_upload_file.py
--- a/rhodecode/apps/file_store/tests/test_upload_file.py
+++ b/rhodecode/apps/file_store/tests/test_upload_file.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/utils.py b/rhodecode/apps/file_store/utils.py
--- a/rhodecode/apps/file_store/utils.py
+++ b/rhodecode/apps/file_store/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/file_store/views.py b/rhodecode/apps/file_store/views.py
--- a/rhodecode/apps/file_store/views.py
+++ b/rhodecode/apps/file_store/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/gist/__init__.py b/rhodecode/apps/gist/__init__.py
--- a/rhodecode/apps/gist/__init__.py
+++ b/rhodecode/apps/gist/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/gist/tests/__init__.py b/rhodecode/apps/gist/tests/__init__.py
--- a/rhodecode/apps/gist/tests/__init__.py
+++ b/rhodecode/apps/gist/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/gist/tests/test_admin_gists.py b/rhodecode/apps/gist/tests/test_admin_gists.py
--- a/rhodecode/apps/gist/tests/test_admin_gists.py
+++ b/rhodecode/apps/gist/tests/test_admin_gists.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/gist/views.py b/rhodecode/apps/gist/views.py
--- a/rhodecode/apps/gist/views.py
+++ b/rhodecode/apps/gist/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 RhodeCode GmbH
+# Copyright (C) 2013-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/home/__init__.py b/rhodecode/apps/home/__init__.py
--- a/rhodecode/apps/home/__init__.py
+++ b/rhodecode/apps/home/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/home/tests/__init__.py b/rhodecode/apps/home/tests/__init__.py
--- a/rhodecode/apps/home/tests/__init__.py
+++ b/rhodecode/apps/home/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/home/tests/test_get_goto_switched_data.py b/rhodecode/apps/home/tests/test_get_goto_switched_data.py
--- a/rhodecode/apps/home/tests/test_get_goto_switched_data.py
+++ b/rhodecode/apps/home/tests/test_get_goto_switched_data.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -27,7 +27,7 @@ from rhodecode.model.repo_group import R
from rhodecode.model.db import Session, Repository, RepoGroup
from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/home/tests/test_get_repo_list_data.py b/rhodecode/apps/home/tests/test_get_repo_list_data.py
--- a/rhodecode/apps/home/tests/test_get_repo_list_data.py
+++ b/rhodecode/apps/home/tests/test_get_repo_list_data.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,7 +22,7 @@ from rhodecode.model.db import Repositor
from rhodecode.lib.ext_json import json
from rhodecode.tests import TestController
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/home/tests/test_get_user_data.py b/rhodecode/apps/home/tests/test_get_user_data.py
--- a/rhodecode/apps/home/tests/test_get_user_data.py
+++ b/rhodecode/apps/home/tests/test_get_user_data.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,7 +20,7 @@ import pytest
from rhodecode.lib.ext_json import json
from rhodecode.tests import TestController
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/home/tests/test_get_user_group_data.py b/rhodecode/apps/home/tests/test_get_user_group_data.py
--- a/rhodecode/apps/home/tests/test_get_user_group_data.py
+++ b/rhodecode/apps/home/tests/test_get_user_group_data.py
@@ -1,23 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# 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 Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -40,7 +21,7 @@ import pytest
from rhodecode.lib.ext_json import json
from rhodecode.tests import TestController
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/home/tests/test_home.py b/rhodecode/apps/home/tests/test_home.py
--- a/rhodecode/apps/home/tests/test_home.py
+++ b/rhodecode/apps/home/tests/test_home.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -24,7 +24,7 @@ from rhodecode.model.db import Repositor
from rhodecode.model.meta import Session
from rhodecode.model.settings import SettingsModel
from rhodecode.tests import TestController
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/home/views.py b/rhodecode/apps/home/views.py
--- a/rhodecode/apps/home/views.py
+++ b/rhodecode/apps/home/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/hovercards/__init__.py b/rhodecode/apps/hovercards/__init__.py
--- a/rhodecode/apps/hovercards/__init__.py
+++ b/rhodecode/apps/hovercards/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2018-2023 RhodeCode GmbH
+# Copyright (C) 2018-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/hovercards/views.py b/rhodecode/apps/hovercards/views.py
--- a/rhodecode/apps/hovercards/views.py
+++ b/rhodecode/apps/hovercards/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/journal/__init__.py b/rhodecode/apps/journal/__init__.py
--- a/rhodecode/apps/journal/__init__.py
+++ b/rhodecode/apps/journal/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/journal/tests/__init__.py b/rhodecode/apps/journal/tests/__init__.py
--- a/rhodecode/apps/journal/tests/__init__.py
+++ b/rhodecode/apps/journal/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/journal/tests/test_journal.py b/rhodecode/apps/journal/tests/test_journal.py
--- a/rhodecode/apps/journal/tests/test_journal.py
+++ b/rhodecode/apps/journal/tests/test_journal.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/journal/views.py b/rhodecode/apps/journal/views.py
--- a/rhodecode/apps/journal/views.py
+++ b/rhodecode/apps/journal/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/login/__init__.py b/rhodecode/apps/login/__init__.py
--- a/rhodecode/apps/login/__init__.py
+++ b/rhodecode/apps/login/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/login/tests/test_2fa.py b/rhodecode/apps/login/tests/test_2fa.py
--- a/rhodecode/apps/login/tests/test_2fa.py
+++ b/rhodecode/apps/login/tests/test_2fa.py
@@ -3,7 +3,7 @@ import mock
from rhodecode.lib.type_utils import AttributeDict
from rhodecode.model.meta import Session
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
from rhodecode.model.settings import SettingsModel
diff --git a/rhodecode/apps/login/tests/test_login.py b/rhodecode/apps/login/tests/test_login.py
--- a/rhodecode/apps/login/tests/test_login.py
+++ b/rhodecode/apps/login/tests/test_login.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -31,7 +31,7 @@ from rhodecode.model.meta import Session
from rhodecode.tests import (
assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
no_newline_id_generator)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/login/tests/test_password_reset.py b/rhodecode/apps/login/tests/test_password_reset.py
--- a/rhodecode/apps/login/tests/test_password_reset.py
+++ b/rhodecode/apps/login/tests/test_password_reset.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,7 +22,7 @@ from rhodecode.lib import helpers as h
from rhodecode.tests import (
TestController, clear_cache_regions,
TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.utils import AssertResponse
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/login/tests/test_register_captcha.py b/rhodecode/apps/login/tests/test_register_captcha.py
--- a/rhodecode/apps/login/tests/test_register_captcha.py
+++ b/rhodecode/apps/login/tests/test_register_captcha.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/login/views.py b/rhodecode/apps/login/views.py
--- a/rhodecode/apps/login/views.py
+++ b/rhodecode/apps/login/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/my_account/__init__.py b/rhodecode/apps/my_account/__init__.py
--- a/rhodecode/apps/my_account/__init__.py
+++ b/rhodecode/apps/my_account/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/my_account/tests/__init__.py b/rhodecode/apps/my_account/tests/__init__.py
--- a/rhodecode/apps/my_account/tests/__init__.py
+++ b/rhodecode/apps/my_account/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py
--- a/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,7 +22,7 @@ from rhodecode.apps._base import ADMIN_P
from rhodecode.model.db import User
from rhodecode.tests import (
TestController, assert_session_flash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/my_account/tests/test_my_account_edit.py b/rhodecode/apps/my_account/tests/test_my_account_edit.py
--- a/rhodecode/apps/my_account/tests/test_my_account_edit.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_edit.py
@@ -1,23 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# 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 Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/my_account/tests/test_my_account_emails.py b/rhodecode/apps/my_account/tests/test_my_account_emails.py
--- a/rhodecode/apps/my_account/tests/test_my_account_emails.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_emails.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -23,7 +23,7 @@ from rhodecode.model.db import User, Use
from rhodecode.tests import (
TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL,
assert_session_flash, TEST_USER_REGULAR_PASS)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/my_account/tests/test_my_account_notifications.py b/rhodecode/apps/my_account/tests/test_my_account_notifications.py
--- a/rhodecode/apps/my_account/tests/test_my_account_notifications.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_notifications.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,7 +21,7 @@ import pytest
from rhodecode.tests import (
TestController, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
from rhodecode.model.db import Notification, User
diff --git a/rhodecode/apps/my_account/tests/test_my_account_password.py b/rhodecode/apps/my_account/tests/test_my_account_password.py
--- a/rhodecode/apps/my_account/tests/test_my_account_password.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_password.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -24,7 +23,7 @@ from rhodecode.lib.auth import check_pas
from rhodecode.model.meta import Session
from rhodecode.model.user import UserModel
from rhodecode.tests import assert_session_flash, TestController
-from rhodecode.tests.fixture import Fixture, error_function
+from rhodecode.tests.fixtures.rc_fixture import Fixture, error_function
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/my_account/tests/test_my_account_profile.py b/rhodecode/apps/my_account/tests/test_my_account_profile.py
--- a/rhodecode/apps/my_account/tests/test_my_account_profile.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_profile.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,7 +19,7 @@
from rhodecode.tests import (
TestController, TEST_USER_ADMIN_LOGIN,
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/my_account/tests/test_my_account_simple_views.py b/rhodecode/apps/my_account/tests/test_my_account_simple_views.py
--- a/rhodecode/apps/my_account/tests/test_my_account_simple_views.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_simple_views.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -19,7 +18,7 @@
from rhodecode.model.db import User, Repository, UserFollowing
from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py b/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py
--- a/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py
+++ b/rhodecode/apps/my_account/tests/test_my_account_ssh_keys.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,7 +20,7 @@
from rhodecode.model.db import User, UserSshKeys
from rhodecode.tests import TestController, assert_session_flash
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/my_account/views/__init__.py b/rhodecode/apps/my_account/views/__init__.py
--- a/rhodecode/apps/my_account/views/__init__.py
+++ b/rhodecode/apps/my_account/views/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/my_account/views/my_account.py b/rhodecode/apps/my_account/views/my_account.py
--- a/rhodecode/apps/my_account/views/my_account.py
+++ b/rhodecode/apps/my_account/views/my_account.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/my_account/views/my_account_notifications.py b/rhodecode/apps/my_account/views/my_account_notifications.py
--- a/rhodecode/apps/my_account/views/my_account_notifications.py
+++ b/rhodecode/apps/my_account/views/my_account_notifications.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/my_account/views/my_account_ssh_keys.py b/rhodecode/apps/my_account/views/my_account_ssh_keys.py
--- a/rhodecode/apps/my_account/views/my_account_ssh_keys.py
+++ b/rhodecode/apps/my_account/views/my_account_ssh_keys.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ops/__init__.py b/rhodecode/apps/ops/__init__.py
--- a/rhodecode/apps/ops/__init__.py
+++ b/rhodecode/apps/ops/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ops/views.py b/rhodecode/apps/ops/views.py
--- a/rhodecode/apps/ops/views.py
+++ b/rhodecode/apps/ops/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/__init__.py b/rhodecode/apps/repo_group/__init__.py
--- a/rhodecode/apps/repo_group/__init__.py
+++ b/rhodecode/apps/repo_group/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/tests/__init__.py b/rhodecode/apps/repo_group/tests/__init__.py
--- a/rhodecode/apps/repo_group/tests/__init__.py
+++ b/rhodecode/apps/repo_group/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/tests/test_repo_groups_advanced.py b/rhodecode/apps/repo_group/tests/test_repo_groups_advanced.py
--- a/rhodecode/apps/repo_group/tests/test_repo_groups_advanced.py
+++ b/rhodecode/apps/repo_group/tests/test_repo_groups_advanced.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/tests/test_repo_groups_permissions.py b/rhodecode/apps/repo_group/tests/test_repo_groups_permissions.py
--- a/rhodecode/apps/repo_group/tests/test_repo_groups_permissions.py
+++ b/rhodecode/apps/repo_group/tests/test_repo_groups_permissions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/tests/test_repo_groups_settings.py b/rhodecode/apps/repo_group/tests/test_repo_groups_settings.py
--- a/rhodecode/apps/repo_group/tests/test_repo_groups_settings.py
+++ b/rhodecode/apps/repo_group/tests/test_repo_groups_settings.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/views/__init__.py b/rhodecode/apps/repo_group/views/__init__.py
--- a/rhodecode/apps/repo_group/views/__init__.py
+++ b/rhodecode/apps/repo_group/views/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/views/repo_group_advanced.py b/rhodecode/apps/repo_group/views/repo_group_advanced.py
--- a/rhodecode/apps/repo_group/views/repo_group_advanced.py
+++ b/rhodecode/apps/repo_group/views/repo_group_advanced.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/views/repo_group_permissions.py b/rhodecode/apps/repo_group/views/repo_group_permissions.py
--- a/rhodecode/apps/repo_group/views/repo_group_permissions.py
+++ b/rhodecode/apps/repo_group/views/repo_group_permissions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repo_group/views/repo_group_settings.py b/rhodecode/apps/repo_group/views/repo_group_settings.py
--- a/rhodecode/apps/repo_group/views/repo_group_settings.py
+++ b/rhodecode/apps/repo_group/views/repo_group_settings.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/__init__.py b/rhodecode/apps/repository/__init__.py
--- a/rhodecode/apps/repository/__init__.py
+++ b/rhodecode/apps/repository/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/__init__.py b/rhodecode/apps/repository/tests/__init__.py
--- a/rhodecode/apps/repository/tests/__init__.py
+++ b/rhodecode/apps/repository/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_pull_requests_list.py b/rhodecode/apps/repository/tests/test_pull_requests_list.py
--- a/rhodecode/apps/repository/tests/test_pull_requests_list.py
+++ b/rhodecode/apps/repository/tests/test_pull_requests_list.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_bookmarks.py b/rhodecode/apps/repository/tests/test_repo_bookmarks.py
--- a/rhodecode/apps/repository/tests/test_repo_bookmarks.py
+++ b/rhodecode/apps/repository/tests/test_repo_bookmarks.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_branches.py b/rhodecode/apps/repository/tests/test_repo_branches.py
--- a/rhodecode/apps/repository/tests/test_repo_branches.py
+++ b/rhodecode/apps/repository/tests/test_repo_branches.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_changelog.py b/rhodecode/apps/repository/tests/test_repo_changelog.py
--- a/rhodecode/apps/repository/tests/test_repo_changelog.py
+++ b/rhodecode/apps/repository/tests/test_repo_changelog.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_commit_comments.py b/rhodecode/apps/repository/tests/test_repo_commit_comments.py
--- a/rhodecode/apps/repository/tests/test_repo_commit_comments.py
+++ b/rhodecode/apps/repository/tests/test_repo_commit_comments.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_commits.py b/rhodecode/apps/repository/tests/test_repo_commits.py
--- a/rhodecode/apps/repository/tests/test_repo_commits.py
+++ b/rhodecode/apps/repository/tests/test_repo_commits.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_compare.py b/rhodecode/apps/repository/tests/test_repo_compare.py
--- a/rhodecode/apps/repository/tests/test_repo_compare.py
+++ b/rhodecode/apps/repository/tests/test_repo_compare.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_compare_local.py b/rhodecode/apps/repository/tests/test_repo_compare_local.py
--- a/rhodecode/apps/repository/tests/test_repo_compare_local.py
+++ b/rhodecode/apps/repository/tests/test_repo_compare_local.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py b/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py
--- a/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py
+++ b/rhodecode/apps/repository/tests/test_repo_compare_on_single_file.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,7 +21,7 @@ import pytest
from rhodecode.apps.repository.tests.test_repo_compare import ComparePage
from rhodecode.lib.vcs import nodes
from rhodecode.lib.vcs.backends.base import EmptyCommit
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.utils import commit_change
from rhodecode.tests.routes import route_path
@@ -166,14 +165,15 @@ class TestSideBySideDiff(object):
response.mustcontain('Collapse 2 commits')
response.mustcontain('123 file changed')
- response.mustcontain(
- 'r%s:%s...r%s:%s' % (
- commit1.idx, commit1.short_id, commit2.idx, commit2.short_id))
+ response.mustcontain(f'r{commit1.idx}:{commit1.short_id}...r{commit2.idx}:{commit2.short_id}')
response.mustcontain(f_path)
- @pytest.mark.xfail(reason='GIT does not handle empty commit compare correct (missing 1 commit)')
+ #@pytest.mark.xfail(reason='GIT does not handle empty commit compare correct (missing 1 commit)')
def test_diff_side_by_side_from_0_commit_with_file_filter(self, app, backend, backend_stub):
+ if backend.alias == 'git':
+ pytest.skip('GIT does not handle empty commit compare correct (missing 1 commit)')
+
f_path = b'test_sidebyside_file.py'
commit1_content = b'content-25d7e49c18b159446c\n'
commit2_content = b'content-603d6c72c46d953420\n'
@@ -200,9 +200,7 @@ class TestSideBySideDiff(object):
response.mustcontain('Collapse 2 commits')
response.mustcontain('1 file changed')
- response.mustcontain(
- 'r%s:%s...r%s:%s' % (
- commit1.idx, commit1.short_id, commit2.idx, commit2.short_id))
+ response.mustcontain(f'r{commit1.idx}:{commit1.short_id}...r{commit2.idx}:{commit2.short_id}')
response.mustcontain(f_path)
diff --git a/rhodecode/apps/repository/tests/test_repo_feed.py b/rhodecode/apps/repository/tests/test_repo_feed.py
--- a/rhodecode/apps/repository/tests/test_repo_feed.py
+++ b/rhodecode/apps/repository/tests/test_repo_feed.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_files.py b/rhodecode/apps/repository/tests/test_repo_files.py
--- a/rhodecode/apps/repository/tests/test_repo_files.py
+++ b/rhodecode/apps/repository/tests/test_repo_files.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -33,7 +32,7 @@ from rhodecode.lib.vcs.conf import setti
from rhodecode.model.db import Session, Repository
from rhodecode.tests import assert_session_flash
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
@@ -42,207 +41,171 @@ fixture = Fixture()
def get_node_history(backend_type):
return {
- 'hg': json.loads(fixture.load_resource('hg_node_history_response.json')),
- 'git': json.loads(fixture.load_resource('git_node_history_response.json')),
- 'svn': json.loads(fixture.load_resource('svn_node_history_response.json')),
+ "hg": json.loads(fixture.load_resource("hg_node_history_response.json")),
+ "git": json.loads(fixture.load_resource("git_node_history_response.json")),
+ "svn": json.loads(fixture.load_resource("svn_node_history_response.json")),
}[backend_type]
def assert_files_in_response(response, files, params):
- template = (
- 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
+ template = 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"'
_assert_items_in_response(response, files, template, params)
def assert_dirs_in_response(response, dirs, params):
- template = (
- 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
+ template = 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"'
_assert_items_in_response(response, dirs, template, params)
def _assert_items_in_response(response, items, template, params):
for item in items:
- item_params = {'name': item}
+ item_params = {"name": item}
item_params.update(params)
response.mustcontain(template % item_params)
def assert_timeago_in_response(response, items, params):
for item in items:
- response.mustcontain(h.age_component(params['date']))
+ response.mustcontain(h.age_component(params["date"]))
@pytest.mark.usefixtures("app")
class TestFilesViews(object):
-
def test_show_files(self, backend):
- response = self.app.get(
- route_path('repo_files',
- repo_name=backend.repo_name,
- commit_id='tip', f_path='/'))
+ response = self.app.get(route_path("repo_files", repo_name=backend.repo_name, commit_id="tip", f_path=""))
commit = backend.repo.get_commit()
- params = {
- 'repo_name': backend.repo_name,
- 'commit_id': commit.raw_id,
- 'date': commit.date
- }
- assert_dirs_in_response(response, ['docs', 'vcs'], params)
+ params = {"repo_name": backend.repo_name, "commit_id": commit.raw_id, "date": commit.date}
+ assert_dirs_in_response(response, ["docs", "vcs"], params)
files = [
- '.gitignore',
- '.hgignore',
- '.hgtags',
+ ".gitignore",
+ ".hgignore",
+ ".hgtags",
# TODO: missing in Git
# '.travis.yml',
- 'MANIFEST.in',
- 'README.rst',
+ "MANIFEST.in",
+ "README.rst",
# TODO: File is missing in svn repository
# 'run_test_and_report.sh',
- 'setup.cfg',
- 'setup.py',
- 'test_and_report.sh',
- 'tox.ini',
+ "setup.cfg",
+ "setup.py",
+ "test_and_report.sh",
+ "tox.ini",
]
assert_files_in_response(response, files, params)
assert_timeago_in_response(response, files, params)
def test_show_files_links_submodules_with_absolute_url(self, backend_hg):
- repo = backend_hg['subrepos']
- response = self.app.get(
- route_path('repo_files',
- repo_name=repo.repo_name,
- commit_id='tip', f_path='/'))
+ repo = backend_hg["subrepos"]
+ response = self.app.get(route_path("repo_files", repo_name=repo.repo_name, commit_id="tip", f_path=""))
assert_response = response.assert_response()
- assert_response.contains_one_link(
- 'absolute-path @ 000000000000', 'http://example.com/absolute-path')
+ assert_response.contains_one_link("absolute-path @ 000000000000", "http://example.com/absolute-path")
- def test_show_files_links_submodules_with_absolute_url_subpaths(
- self, backend_hg):
- repo = backend_hg['subrepos']
- response = self.app.get(
- route_path('repo_files',
- repo_name=repo.repo_name,
- commit_id='tip', f_path='/'))
+ def test_show_files_links_submodules_with_absolute_url_subpaths(self, backend_hg):
+ repo = backend_hg["subrepos"]
+ response = self.app.get(route_path("repo_files", repo_name=repo.repo_name, commit_id="tip", f_path=""))
assert_response = response.assert_response()
- assert_response.contains_one_link(
- 'subpaths-path @ 000000000000',
- 'http://sub-base.example.com/subpaths-path')
+ assert_response.contains_one_link("subpaths-path @ 000000000000", "http://sub-base.example.com/subpaths-path")
@pytest.mark.xfail_backends("svn", reason="Depends on branch support")
def test_files_menu(self, backend):
new_branch = "temp_branch_name"
- commits = [
- {'message': 'a'},
- {'message': 'b', 'branch': new_branch}
- ]
+ commits = [{"message": "a"}, {"message": "b", "branch": new_branch}]
backend.create_repo(commits)
backend.repo.landing_rev = f"branch:{new_branch}"
Session().commit()
# get response based on tip and not new commit
- response = self.app.get(
- route_path('repo_files',
- repo_name=backend.repo_name,
- commit_id='tip', f_path='/'))
+ response = self.app.get(route_path("repo_files", repo_name=backend.repo_name, commit_id="tip", f_path=""))
# make sure Files menu url is not tip but new commit
landing_rev = backend.repo.landing_ref_name
- files_url = route_path('repo_files:default_path',
- repo_name=backend.repo_name,
- commit_id=landing_rev, params={'at': landing_rev})
+ files_url = route_path(
+ "repo_files:default_path", repo_name=backend.repo_name, commit_id=landing_rev, params={"at": landing_rev}
+ )
- assert landing_rev != 'tip'
+ assert landing_rev != "tip"
response.mustcontain(f'add a new file '
- repo_file_upload_url = route_path(
- 'repo_files_upload_file',
- repo_name=repo.repo_name,
- commit_id=0, f_path='')
+ repo_file_upload_url = route_path("repo_files_upload_file", repo_name=repo.repo_name, commit_id=0, f_path="")
upload_new = f'upload a new file '
- assert_session_flash(
- response,
- 'There are no files yet. Click here to %s or %s.' % (add_new, upload_new)
- )
+ assert_session_flash(response, "There are no files yet. Click here to %s or %s." % (add_new, upload_new))
def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
- self, backend_stub, autologin_regular_user):
+ self, backend_stub, autologin_regular_user
+ ):
repo = backend_stub.create_repo()
# init session for anon user
- route_path('repo_summary', repo_name=repo.repo_name)
+ route_path("repo_summary", repo_name=repo.repo_name)
- repo_file_add_url = route_path(
- 'repo_files_add_file',
- repo_name=repo.repo_name,
- commit_id=0, f_path='')
+ repo_file_add_url = route_path("repo_files_add_file", repo_name=repo.repo_name, commit_id=0, f_path="")
- response = self.app.get(
- route_path('repo_files',
- repo_name=repo.repo_name,
- commit_id='tip', f_path='/'))
+ response = self.app.get(route_path("repo_files", repo_name=repo.repo_name, commit_id="tip", f_path=""))
assert_session_flash(response, no_=repo_file_add_url)
- @pytest.mark.parametrize('file_node', [
- b'archive/file.zip',
- b'diff/my-file.txt',
- b'render.py',
- b'render',
- b'remove_file',
- b'remove_file/to-delete.txt',
- ])
+ @pytest.mark.parametrize(
+ "file_node",
+ [
+ b"archive/file.zip",
+ b"diff/my-file.txt",
+ b"render.py",
+ b"render",
+ b"remove_file",
+ b"remove_file/to-delete.txt",
+ ],
+ )
def test_file_names_equal_to_routes_parts(self, backend, file_node):
backend.create_repo()
backend.ensure_file(file_node)
self.app.get(
- route_path('repo_files',
- repo_name=backend.repo_name,
- commit_id='tip', f_path=safe_str(file_node)),
- status=200)
+ route_path("repo_files", repo_name=backend.repo_name, commit_id="tip", f_path=safe_str(file_node)),
+ status=200,
+ )
class TestAdjustFilePathForSvn(object):
@@ -1126,20 +1045,20 @@ class TestAdjustFilePathForSvn(object):
"""
def test_returns_path_relative_to_matched_reference(self):
- repo = self._repo(branches=['trunk'])
- self.assert_file_adjustment('trunk/file', 'file', repo)
+ repo = self._repo(branches=["trunk"])
+ self.assert_file_adjustment("trunk/file", "file", repo)
def test_does_not_modify_file_if_no_reference_matches(self):
- repo = self._repo(branches=['trunk'])
- self.assert_file_adjustment('notes/file', 'notes/file', repo)
+ repo = self._repo(branches=["trunk"])
+ self.assert_file_adjustment("notes/file", "notes/file", repo)
def test_does_not_adjust_partial_directory_names(self):
- repo = self._repo(branches=['trun'])
- self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
+ repo = self._repo(branches=["trun"])
+ self.assert_file_adjustment("trunk/file", "trunk/file", repo)
def test_is_robust_to_patterns_which_prefix_other_patterns(self):
- repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
- self.assert_file_adjustment('trunk/new/file', 'file', repo)
+ repo = self._repo(branches=["trunk", "trunk/new", "trunk/old"])
+ self.assert_file_adjustment("trunk/new/file", "file", repo)
def assert_file_adjustment(self, f_path, expected, repo):
result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
@@ -1147,6 +1066,6 @@ class TestAdjustFilePathForSvn(object):
def _repo(self, branches=None):
repo = mock.Mock()
- repo.branches = OrderedDict((name, '0') for name in branches or [])
+ repo.branches = OrderedDict((name, "0") for name in branches or [])
repo.tags = {}
return repo
diff --git a/rhodecode/apps/repository/tests/test_repo_forks.py b/rhodecode/apps/repository/tests/test_repo_forks.py
--- a/rhodecode/apps/repository/tests/test_repo_forks.py
+++ b/rhodecode/apps/repository/tests/test_repo_forks.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,7 +20,7 @@ import pytest
from rhodecode.tests import TestController, assert_session_flash, HG_FORK, GIT_FORK
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.lib import helpers as h
from rhodecode.model.db import Repository
diff --git a/rhodecode/apps/repository/tests/test_repo_issue_tracker.py b/rhodecode/apps/repository/tests/test_repo_issue_tracker.py
--- a/rhodecode/apps/repository/tests/test_repo_issue_tracker.py
+++ b/rhodecode/apps/repository/tests/test_repo_issue_tracker.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_maintainance.py b/rhodecode/apps/repository/tests/test_repo_maintainance.py
--- a/rhodecode/apps/repository/tests/test_repo_maintainance.py
+++ b/rhodecode/apps/repository/tests/test_repo_maintainance.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,7 +20,7 @@ import pytest
from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/repository/tests/test_repo_permissions.py b/rhodecode/apps/repository/tests/test_repo_permissions.py
--- a/rhodecode/apps/repository/tests/test_repo_permissions.py
+++ b/rhodecode/apps/repository/tests/test_repo_permissions.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_pullrequests.py b/rhodecode/apps/repository/tests/test_repo_pullrequests.py
--- a/rhodecode/apps/repository/tests/test_repo_pullrequests.py
+++ b/rhodecode/apps/repository/tests/test_repo_pullrequests.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -15,6 +15,9 @@
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
+import logging
+import os
+
import mock
import pytest
@@ -41,7 +44,7 @@ from rhodecode.tests import (
TEST_USER_ADMIN_LOGIN,
TEST_USER_REGULAR_LOGIN,
)
-from rhodecode.tests.fixture_mods.fixture_utils import PRTestUtility
+from rhodecode.tests.fixtures.fixture_utils import PRTestUtility
from rhodecode.tests.routes import route_path
@@ -1050,7 +1053,6 @@ class TestPullrequestsView(object):
)
assert len(notifications.all()) == 2
- @pytest.mark.xfail(reason="unable to fix this test after python3 migration")
def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token):
commits = [
{
@@ -1125,20 +1127,38 @@ class TestPullrequestsView(object):
response.mustcontain(no=["content_of_ancestor-child"])
response.mustcontain("content_of_change")
- def test_merge_pull_request_enabled(self, pr_util, csrf_token):
- # Clear any previous calls to rcextensions
- rhodecode.EXTENSIONS.calls.clear()
+ def test_merge_pull_request_enabled(self, pr_util, csrf_token, rcextensions_modification):
pull_request = pr_util.create_pull_request(approved=True, mergeable=True)
pull_request_id = pull_request.pull_request_id
- repo_name = (pull_request.target_repo.scm_instance().name,)
+ repo_name = pull_request.target_repo.scm_instance().name
url = route_path(
"pullrequest_merge",
- repo_name=str(repo_name[0]),
+ repo_name=repo_name,
pull_request_id=pull_request_id,
)
- response = self.app.post(url, params={"csrf_token": csrf_token}).follow()
+
+ rcstack_location = os.path.dirname(self.app._pyramid_registry.settings['__file__'])
+ rc_ext_location = os.path.join(rcstack_location, 'rcextension-output.txt')
+
+
+ mods = [
+ ('_push_hook',
+ f"""
+ import os
+ action = kwargs['action']
+ commit_ids = kwargs['commit_ids']
+ with open('{rc_ext_location}', 'w') as f:
+ f.write('test-execution'+os.linesep)
+ f.write(f'{{action}}'+os.linesep)
+ f.write(f'{{commit_ids}}'+os.linesep)
+ return HookResponse(0, 'HOOK_TEST')
+ """)
+ ]
+ # Add the hook
+ with rcextensions_modification(rcstack_location, mods, create_if_missing=True, force_create=True):
+ response = self.app.post(url, params={"csrf_token": csrf_token}).follow()
pull_request = PullRequest.get(pull_request_id)
@@ -1162,12 +1182,39 @@ class TestPullrequestsView(object):
assert actions[-1].action == "user.push"
assert actions[-1].action_data["commit_ids"] == pr_commit_ids
- # Check post_push rcextension was really executed
- push_calls = rhodecode.EXTENSIONS.calls["_push_hook"]
- assert len(push_calls) == 1
- unused_last_call_args, last_call_kwargs = push_calls[0]
- assert last_call_kwargs["action"] == "push"
- assert last_call_kwargs["commit_ids"] == pr_commit_ids
+ with open(rc_ext_location) as f:
+ f_data = f.read()
+ assert 'test-execution' in f_data
+ for commit_id in pr_commit_ids:
+ assert f'{commit_id}' in f_data
+
+ def test_merge_pull_request_forbidden_by_pre_push_hook(self, pr_util, csrf_token, rcextensions_modification, caplog):
+ caplog.set_level(logging.WARNING, logger="rhodecode.model.pull_request")
+
+ pull_request = pr_util.create_pull_request(approved=True, mergeable=True)
+ pull_request_id = pull_request.pull_request_id
+ repo_name = pull_request.target_repo.scm_instance().name
+
+ url = route_path(
+ "pullrequest_merge",
+ repo_name=repo_name,
+ pull_request_id=pull_request_id,
+ )
+
+ rcstack_location = os.path.dirname(self.app._pyramid_registry.settings['__file__'])
+
+ mods = [
+ ('_pre_push_hook',
+ f"""
+ return HookResponse(1, 'HOOK_TEST_FORBIDDEN')
+ """)
+ ]
+ # Add the hook
+ with rcextensions_modification(rcstack_location, mods, create_if_missing=True, force_create=True):
+ self.app.post(url, params={"csrf_token": csrf_token})
+
+ assert 'Merge failed, not updating the pull request.' in [r[2] for r in caplog.record_tuples]
+
def test_merge_pull_request_disabled(self, pr_util, csrf_token):
pull_request = pr_util.create_pull_request(mergeable=False)
@@ -1523,7 +1570,6 @@ class TestPullrequestsView(object):
assert pull_request.revisions == [commit_ids["change-rebased"]]
-
def test_remove_pull_request_branch(self, backend_git, csrf_token):
branch_name = "development"
commits = [
diff --git a/rhodecode/apps/repository/tests/test_repo_settings.py b/rhodecode/apps/repository/tests/test_repo_settings.py
--- a/rhodecode/apps/repository/tests/test_repo_settings.py
+++ b/rhodecode/apps/repository/tests/test_repo_settings.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -26,7 +25,7 @@ from rhodecode.model.db import Repositor
from rhodecode.model.meta import Session
from rhodecode.tests import (
TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/repository/tests/test_repo_settings_advanced.py b/rhodecode/apps/repository/tests/test_repo_settings_advanced.py
--- a/rhodecode/apps/repository/tests/test_repo_settings_advanced.py
+++ b/rhodecode/apps/repository/tests/test_repo_settings_advanced.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -24,7 +23,7 @@ from rhodecode.model.db import Repositor
from rhodecode.model.repo import RepoModel
from rhodecode.tests import (
HG_REPO, GIT_REPO, assert_session_flash, no_newline_id_generator)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.utils import repo_on_filesystem
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/repository/tests/test_repo_summary.py b/rhodecode/apps/repository/tests/test_repo_summary.py
--- a/rhodecode/apps/repository/tests/test_repo_summary.py
+++ b/rhodecode/apps/repository/tests/test_repo_summary.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -31,7 +31,7 @@ from rhodecode.model.meta import Session
from rhodecode.model.repo import RepoModel
from rhodecode.model.scm import ScmModel
from rhodecode.tests import assert_session_flash
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/repository/tests/test_repo_tags.py b/rhodecode/apps/repository/tests/test_repo_tags.py
--- a/rhodecode/apps/repository/tests/test_repo_tags.py
+++ b/rhodecode/apps/repository/tests/test_repo_tags.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/tests/test_repo_vcs_settings.py b/rhodecode/apps/repository/tests/test_repo_vcs_settings.py
--- a/rhodecode/apps/repository/tests/test_repo_vcs_settings.py
+++ b/rhodecode/apps/repository/tests/test_repo_vcs_settings.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -30,7 +29,7 @@ from rhodecode.model.user import UserMod
from rhodecode.tests import (
login_user_session, logout_user_session,
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.utils import AssertResponse
from rhodecode.tests.routes import route_path
@@ -48,6 +47,7 @@ class TestVcsSettings(object):
'extensions_evolve': False,
'phases_publish': 'False',
'rhodecode_pr_merge_enabled': False,
+ 'rhodecode_auto_merge_enabled': False,
'rhodecode_use_outdated_comments': False,
'new_svn_branch': '',
'new_svn_tag': ''
@@ -59,7 +59,7 @@ class TestVcsSettings(object):
response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
expected_settings = (
- 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
+ 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled', 'rhodecode_auto_merge_enabled',
'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
'hooks_outgoing_pull_logger'
)
diff --git a/rhodecode/apps/repository/tests/test_vcs_settings.py b/rhodecode/apps/repository/tests/test_vcs_settings.py
--- a/rhodecode/apps/repository/tests/test_vcs_settings.py
+++ b/rhodecode/apps/repository/tests/test_vcs_settings.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -32,16 +31,13 @@ class TestAdminRepoVcsSettings(object):
@pytest.mark.parametrize('setting_name, setting_backends', [
('hg_use_rebase_for_merging', ['hg']),
])
- def test_labs_settings_visible_if_enabled(
- self, setting_name, setting_backends, backend):
+ def test_labs_settings_visible_if_enabled(self, setting_name, setting_backends, backend):
if backend.alias not in setting_backends:
pytest.skip('Setting not available for backend {}'.format(backend))
- vcs_settings_url = route_path(
- 'edit_repo_vcs', repo_name=backend.repo.repo_name)
+ vcs_settings_url = route_path('edit_repo_vcs', repo_name=backend.repo.repo_name)
- with mock.patch.dict(
- rhodecode.CONFIG, {'labs_settings_active': 'true'}):
+ with mock.patch.dict(rhodecode.CONFIG, {'labs_settings_active': 'true'}):
response = self.app.get(vcs_settings_url)
assertr = response.assert_response()
diff --git a/rhodecode/apps/repository/utils.py b/rhodecode/apps/repository/utils.py
--- a/rhodecode/apps/repository/utils.py
+++ b/rhodecode/apps/repository/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/__init__.py b/rhodecode/apps/repository/views/__init__.py
--- a/rhodecode/apps/repository/views/__init__.py
+++ b/rhodecode/apps/repository/views/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_artifacts.py b/rhodecode/apps/repository/views/repo_artifacts.py
--- a/rhodecode/apps/repository/views/repo_artifacts.py
+++ b/rhodecode/apps/repository/views/repo_artifacts.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_audit_logs.py b/rhodecode/apps/repository/views/repo_audit_logs.py
--- a/rhodecode/apps/repository/views/repo_audit_logs.py
+++ b/rhodecode/apps/repository/views/repo_audit_logs.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_automation.py b/rhodecode/apps/repository/views/repo_automation.py
--- a/rhodecode/apps/repository/views/repo_automation.py
+++ b/rhodecode/apps/repository/views/repo_automation.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_bookmarks.py b/rhodecode/apps/repository/views/repo_bookmarks.py
--- a/rhodecode/apps/repository/views/repo_bookmarks.py
+++ b/rhodecode/apps/repository/views/repo_bookmarks.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_branch_permissions.py b/rhodecode/apps/repository/views/repo_branch_permissions.py
--- a/rhodecode/apps/repository/views/repo_branch_permissions.py
+++ b/rhodecode/apps/repository/views/repo_branch_permissions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_branches.py b/rhodecode/apps/repository/views/repo_branches.py
--- a/rhodecode/apps/repository/views/repo_branches.py
+++ b/rhodecode/apps/repository/views/repo_branches.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_caches.py b/rhodecode/apps/repository/views/repo_caches.py
--- a/rhodecode/apps/repository/views/repo_caches.py
+++ b/rhodecode/apps/repository/views/repo_caches.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_changelog.py b/rhodecode/apps/repository/views/repo_changelog.py
--- a/rhodecode/apps/repository/views/repo_changelog.py
+++ b/rhodecode/apps/repository/views/repo_changelog.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -33,7 +33,7 @@ from rhodecode.lib.auth import (
from rhodecode.lib.graphmod import _colored, _dagwalker
from rhodecode.lib.helpers import RepoPage
from rhodecode.lib.utils2 import str2bool
-from rhodecode.lib.str_utils import safe_int, safe_str
+from rhodecode.lib.str_utils import safe_int, safe_str, safe_bytes
from rhodecode.lib.vcs.exceptions import (
RepositoryError, CommitDoesNotExistError,
CommitError, NodeDoesNotExistError, EmptyRepositoryError)
@@ -204,10 +204,9 @@ class RepoChangelogView(RepoAppView):
log.debug('generating changelog for path %s', f_path)
# get the history for the file !
base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
-
+ bytes_path = safe_bytes(f_path)
try:
- collection = base_commit.get_path_history(
- f_path, limit=hist_limit, pre_load=pre_load)
+ collection = base_commit.get_path_history(bytes_path, limit=hist_limit, pre_load=pre_load)
if collection and partial_xhr:
# for ajax call we remove first one since we're looking
# at it right now in the context of a file commit
@@ -216,7 +215,7 @@ class RepoChangelogView(RepoAppView):
# this node is not present at tip!
try:
commit = self._get_commit_or_redirect(commit_id)
- collection = commit.get_path_history(f_path)
+ collection = commit.get_path_history(bytes_path)
except RepositoryError as e:
h.flash(safe_str(e), category='warning')
redirect_url = h.route_path(
@@ -310,9 +309,8 @@ class RepoChangelogView(RepoAppView):
log.exception(safe_str(e))
raise HTTPFound(
h.route_path('repo_commits', repo_name=self.db_repo_name))
-
- collection = base_commit.get_path_history(
- f_path, limit=hist_limit, pre_load=pre_load)
+ bytes_path = safe_bytes(f_path)
+ collection = base_commit.get_path_history(bytes_path, limit=hist_limit, pre_load=pre_load)
collection = list(reversed(collection))
else:
collection = self.rhodecode_vcs_repo.get_commits(
diff --git a/rhodecode/apps/repository/views/repo_checks.py b/rhodecode/apps/repository/views/repo_checks.py
--- a/rhodecode/apps/repository/views/repo_checks.py
+++ b/rhodecode/apps/repository/views/repo_checks.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py
--- a/rhodecode/apps/repository/views/repo_commits.py
+++ b/rhodecode/apps/repository/views/repo_commits.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -89,8 +89,7 @@ class RepoCommitsView(RepoAppView):
commit_range = commit_id_range.split('...')[:2]
try:
- pre_load = ['affected_files', 'author', 'branch', 'date',
- 'message', 'parents']
+ pre_load = ['author', 'branch', 'date', 'message', 'parents']
if self.rhodecode_vcs_repo.alias == 'hg':
pre_load += ['hidden', 'obsolete', 'phase']
@@ -100,8 +99,7 @@ class RepoCommitsView(RepoAppView):
pre_load=pre_load, translate_tags=False)
commits = list(commits)
else:
- commits = [self.rhodecode_vcs_repo.get_commit(
- commit_id=commit_id_range, pre_load=pre_load)]
+ commits = [self.rhodecode_vcs_repo.get_commit(commit_id=commit_id_range, pre_load=pre_load)]
c.commit_ranges = commits
if not c.commit_ranges:
diff --git a/rhodecode/apps/repository/views/repo_compare.py b/rhodecode/apps/repository/views/repo_compare.py
--- a/rhodecode/apps/repository/views/repo_compare.py
+++ b/rhodecode/apps/repository/views/repo_compare.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_feed.py b/rhodecode/apps/repository/views/repo_feed.py
--- a/rhodecode/apps/repository/views/repo_feed.py
+++ b/rhodecode/apps/repository/views/repo_feed.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_files.py b/rhodecode/apps/repository/views/repo_files.py
--- a/rhodecode/apps/repository/views/repo_files.py
+++ b/rhodecode/apps/repository/views/repo_files.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -40,25 +40,34 @@ from rhodecode.lib import diffs, helpers
from rhodecode.lib import audit_logger
from rhodecode.lib.hash_utils import sha1_safe
from rhodecode.lib.archive_cache import (
- get_archival_cache_store, get_archival_config, ArchiveCacheGenerationLock, archive_iterator)
+ get_archival_cache_store,
+ get_archival_config,
+ ArchiveCacheGenerationLock,
+ archive_iterator,
+)
from rhodecode.lib.str_utils import safe_bytes, convert_special_chars
from rhodecode.lib.view_utils import parse_path_ref
from rhodecode.lib.exceptions import NonRelativePathError
-from rhodecode.lib.codeblocks import (
- filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
+from rhodecode.lib.codeblocks import filenode_as_lines_tokens, filenode_as_annotated_lines_tokens
from rhodecode.lib.utils2 import convert_line_endings, detect_mode
from rhodecode.lib.type_utils import str2bool
from rhodecode.lib.str_utils import safe_str, safe_int, header_safe_str
-from rhodecode.lib.auth import (
- LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired
from rhodecode.lib.vcs import path as vcspath
from rhodecode.lib.vcs.backends.base import EmptyCommit
from rhodecode.lib.vcs.conf import settings
from rhodecode.lib.vcs.nodes import FileNode
from rhodecode.lib.vcs.exceptions import (
- RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
- ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
- NodeDoesNotExistError, CommitError, NodeError)
+ RepositoryError,
+ CommitDoesNotExistError,
+ EmptyRepositoryError,
+ ImproperArchiveTypeError,
+ VCSError,
+ NodeAlreadyExistsError,
+ NodeDoesNotExistError,
+ CommitError,
+ NodeError,
+)
from rhodecode.model.scm import ScmModel
from rhodecode.model.db import Repository
@@ -66,17 +75,17 @@ from rhodecode.model.db import Repositor
log = logging.getLogger(__name__)
-def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
+def get_archive_name(db_repo_id, db_repo_name, commit_sha, ext, subrepos=False, path_sha="", with_hash=True):
# original backward compat name of archive
- clean_name = safe_str(convert_special_chars(db_repo_name).replace('/', '_'))
+ clean_name = safe_str(convert_special_chars(db_repo_name).replace("/", "_"))
# e.g vcsserver-id-abcd-sub-1-abcfdef-archive-all.zip
# vcsserver-id-abcd-sub-0-abcfdef-COMMIT_SHA-PATH_SHA.zip
id_sha = sha1_safe(str(db_repo_id))[:4]
- sub_repo = 'sub-1' if subrepos else 'sub-0'
- commit = commit_sha if with_hash else 'archive'
- path_marker = (path_sha if with_hash else '') or 'all'
- archive_name = f'{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}'
+ sub_repo = "sub-1" if subrepos else "sub-0"
+ commit = commit_sha if with_hash else "archive"
+ path_marker = (path_sha if with_hash else "") or "all"
+ archive_name = f"{clean_name}-id-{id_sha}-{sub_repo}-{commit}-{path_marker}{ext}"
return archive_name
@@ -86,16 +95,15 @@ def get_path_sha(at_path):
def _get_archive_spec(fname):
- log.debug('Detecting archive spec for: `%s`', fname)
+ log.debug("Detecting archive spec for: `%s`", fname)
fileformat = None
ext = None
content_type = None
for a_type, content_type, extension in settings.ARCHIVE_SPECS:
-
if fname.endswith(extension):
fileformat = a_type
- log.debug('archive is of type: %s', fileformat)
+ log.debug("archive is of type: %s", fileformat)
ext = extension
break
@@ -103,13 +111,12 @@ def _get_archive_spec(fname):
raise ValueError()
# left over part of whole fname is the commit
- commit_id = fname[:-len(ext)]
+ commit_id = fname[: -len(ext)]
return commit_id, ext, fileformat, content_type
class RepoFilesView(RepoAppView):
-
@staticmethod
def adjust_file_path_for_svn(f_path, repo):
"""
@@ -118,13 +125,11 @@ class RepoFilesView(RepoAppView):
This is mainly based on prefix matching of the recognized tags and
branches in the underlying repository.
"""
- tags_and_branches = itertools.chain(
- repo.branches.keys(),
- repo.tags.keys())
+ tags_and_branches = itertools.chain(repo.branches.keys(), repo.tags.keys())
tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
for name in tags_and_branches:
- if f_path.startswith(f'{name}/'):
+ if f_path.startswith(f"{name}/"):
f_path = vcspath.relpath(f_path, name)
break
return f_path
@@ -135,66 +140,61 @@ class RepoFilesView(RepoAppView):
c.enable_downloads = self.db_repo.enable_downloads
return c
- def _ensure_not_locked(self, commit_id='tip'):
+ def _ensure_not_locked(self, commit_id="tip"):
_ = self.request.translate
repo = self.db_repo
if repo.enable_locking and repo.locked[0]:
- h.flash(_('This repository has been locked by %s on %s')
- % (h.person_by_id(repo.locked[0]),
- h.format_date(h.time_to_datetime(repo.locked[1]))),
- 'warning')
- files_url = h.route_path(
- 'repo_files:default_path',
- repo_name=self.db_repo_name, commit_id=commit_id)
+ h.flash(
+ _("This repository has been locked by %s on %s")
+ % (h.person_by_id(repo.locked[0]), h.format_date(h.time_to_datetime(repo.locked[1]))),
+ "warning",
+ )
+ files_url = h.route_path("repo_files:default_path", repo_name=self.db_repo_name, commit_id=commit_id)
raise HTTPFound(files_url)
- def forbid_non_head(self, is_head, f_path, commit_id='tip', json_mode=False):
+ def forbid_non_head(self, is_head, f_path, commit_id="tip", json_mode=False):
_ = self.request.translate
if not is_head:
- message = _('Cannot modify file. '
- 'Given commit `{}` is not head of a branch.').format(commit_id)
- h.flash(message, category='warning')
+ message = _("Cannot modify file. " "Given commit `{}` is not head of a branch.").format(commit_id)
+ h.flash(message, category="warning")
if json_mode:
return message
- files_url = h.route_path(
- 'repo_files', repo_name=self.db_repo_name, commit_id=commit_id,
- f_path=f_path)
+ files_url = h.route_path("repo_files", repo_name=self.db_repo_name, commit_id=commit_id, f_path=f_path)
raise HTTPFound(files_url)
- def check_branch_permission(self, branch_name, commit_id='tip', json_mode=False):
+ def check_branch_permission(self, branch_name, commit_id="tip", json_mode=False):
_ = self.request.translate
- rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(
- self.db_repo_name, branch_name)
- if branch_perm and branch_perm not in ['branch.push', 'branch.push_force']:
- message = _('Branch `{}` changes forbidden by rule {}.').format(
- h.escape(branch_name), h.escape(rule))
- h.flash(message, 'warning')
+ rule, branch_perm = self._rhodecode_user.get_rule_and_branch_permission(self.db_repo_name, branch_name)
+ if branch_perm and branch_perm not in ["branch.push", "branch.push_force"]:
+ message = _("Branch `{}` changes forbidden by rule {}.").format(h.escape(branch_name), h.escape(rule))
+ h.flash(message, "warning")
if json_mode:
return message
- files_url = h.route_path(
- 'repo_files:default_path', repo_name=self.db_repo_name, commit_id=commit_id)
+ files_url = h.route_path("repo_files:default_path", repo_name=self.db_repo_name, commit_id=commit_id)
raise HTTPFound(files_url)
def _get_commit_and_path(self):
default_commit_id = self.db_repo.landing_ref_name
- default_f_path = '/'
+ default_f_path = "/"
- commit_id = self.request.matchdict.get(
- 'commit_id', default_commit_id)
+ commit_id = self.request.matchdict.get("commit_id", default_commit_id)
f_path = self._get_f_path(self.request.matchdict, default_f_path)
- return commit_id, f_path
+
+ bytes_path = safe_bytes(f_path)
+ return commit_id, f_path, bytes_path
- def _get_default_encoding(self, c):
- enc_list = getattr(c, 'default_encodings', [])
- return enc_list[0] if enc_list else 'UTF-8'
+ @classmethod
+ def _get_default_encoding(cls, c):
+ enc_list = getattr(c, "default_encodings", [])
+ return enc_list[0] if enc_list else "UTF-8"
def _get_commit_or_redirect(self, commit_id, redirect_after=True):
"""
@@ -213,31 +213,25 @@ class RepoFilesView(RepoAppView):
return None
add_new = upload_new = ""
- if h.HasRepoPermissionAny(
- 'repository.write', 'repository.admin')(self.db_repo_name):
- _url = h.route_path(
- 'repo_files_add_file',
- repo_name=self.db_repo_name, commit_id=0, f_path='')
- add_new = h.link_to(
- _('add a new file'), _url, class_="alert-link")
+ if h.HasRepoPermissionAny("repository.write", "repository.admin")(self.db_repo_name):
+ _url = h.route_path("repo_files_add_file", repo_name=self.db_repo_name, commit_id=0, f_path="")
+ add_new = h.link_to(_("add a new file"), _url, class_="alert-link")
- _url_upld = h.route_path(
- 'repo_files_upload_file',
- repo_name=self.db_repo_name, commit_id=0, f_path='')
- upload_new = h.link_to(
- _('upload a new file'), _url_upld, class_="alert-link")
+ _url_upld = h.route_path("repo_files_upload_file", repo_name=self.db_repo_name, commit_id=0, f_path="")
+ upload_new = h.link_to(_("upload a new file"), _url_upld, class_="alert-link")
- h.flash(h.literal(
- _('There are no files yet. Click here to %s or %s.') % (add_new, upload_new)), category='warning')
- raise HTTPFound(
- h.route_path('repo_summary', repo_name=self.db_repo_name))
+ h.flash(
+ h.literal(_("There are no files yet. Click here to %s or %s.") % (add_new, upload_new)),
+ category="warning",
+ )
+ raise HTTPFound(h.route_path("repo_summary", repo_name=self.db_repo_name))
except (CommitDoesNotExistError, LookupError) as e:
- msg = _('No such commit exists for this repository. Commit: {}').format(commit_id)
- h.flash(msg, category='error')
+ msg = _("No such commit exists for this repository. Commit: {}").format(commit_id)
+ h.flash(msg, category="error")
raise HTTPNotFound()
except RepositoryError as e:
- h.flash(h.escape(safe_str(e)), category='error')
+ h.flash(h.escape(safe_str(e)), category="error")
raise HTTPNotFound()
def _get_filenode_or_redirect(self, commit_obj, path, pre_load=None):
@@ -250,22 +244,22 @@ class RepoFilesView(RepoAppView):
try:
file_node = commit_obj.get_node(path, pre_load=pre_load)
if file_node.is_dir():
- raise RepositoryError('The given path is a directory')
+ raise RepositoryError("The given path is a directory")
except CommitDoesNotExistError:
- log.exception('No such commit exists for this repository')
- h.flash(_('No such commit exists for this repository'), category='error')
+ log.exception("No such commit exists for this repository")
+ h.flash(_("No such commit exists for this repository"), category="error")
raise HTTPNotFound()
except RepositoryError as e:
- log.warning('Repository error while fetching filenode `%s`. Err:%s', path, e)
- h.flash(h.escape(safe_str(e)), category='error')
+ log.warning("Repository error while fetching filenode `%s`. Err:%s", path, e)
+ h.flash(h.escape(safe_str(e)), category="error")
raise HTTPNotFound()
return file_node
def _is_valid_head(self, commit_id, repo, landing_ref):
- branch_name = sha_commit_id = ''
+ branch_name = sha_commit_id = ""
is_head = False
- log.debug('Checking if commit_id `%s` is a head for %s.', commit_id, repo)
+ log.debug("Checking if commit_id `%s` is a head for %s.", commit_id, repo)
for _branch_name, branch_commit_id in repo.branches.items():
# simple case we pass in branch name, it's a HEAD
@@ -301,39 +295,39 @@ class RepoFilesView(RepoAppView):
return branch_name, sha_commit_id, is_head
def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
-
repo_id = self.db_repo.repo_id
force_recache = self.get_recache_flag()
- cache_seconds = safe_int(
- rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
+ cache_seconds = rhodecode.ConfigGet().get_int("rc_cache.cache_repo.expiration_time")
cache_on = not force_recache and cache_seconds > 0
+
log.debug(
- 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
- 'with caching: %s[TTL: %ss]' % (
- repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
+ "Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`"
+ "with caching: %s[TTL: %ss]" % (repo_id, commit_id, f_path, cache_on, cache_seconds or 0)
+ )
- cache_namespace_uid = f'repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}'
- region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
+ cache_namespace_uid = f"repo.{rc_cache.FILE_TREE_CACHE_VER}.{repo_id}"
+ region = rc_cache.get_or_create_region("cache_repo", cache_namespace_uid)
@region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
def compute_file_tree(_name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
- log.debug('Generating cached file tree at for repo_id: %s, %s, %s',
- _repo_id, _commit_id, _f_path)
+ log.debug("Generating cached file tree at for repo_id: %s, %s, %s", _repo_id, _commit_id, _f_path)
c.full_load = _full_load
return render(
- 'rhodecode:templates/files/files_browser_tree.mako',
- self._get_template_context(c), self.request, _at_rev)
+ "rhodecode:templates/files/files_browser_tree.mako",
+ self._get_template_context(c),
+ self.request,
+ _at_rev,
+ )
return compute_file_tree(
- self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
+ self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path, full_load, at_rev
+ )
def create_pure_path(self, *parts):
# Split paths and sanitize them, removing any ../ etc
- sanitized_path = [
- x for x in pathlib.PurePath(*parts).parts
- if x not in ['.', '..']]
+ sanitized_path = [x for x in pathlib.PurePath(*parts).parts if x not in [".", ".."]]
pure_path = pathlib.PurePath(*sanitized_path)
return pure_path
@@ -341,10 +335,7 @@ class RepoFilesView(RepoAppView):
def _is_lf_enabled(self, target_repo):
lf_enabled = False
- lf_key_for_vcs_map = {
- 'hg': 'extensions_largefiles',
- 'git': 'vcs_git_lfs_enabled'
- }
+ lf_key_for_vcs_map = {"hg": "extensions_largefiles", "git": "vcs_git_lfs_enabled"}
lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
@@ -354,66 +345,77 @@ class RepoFilesView(RepoAppView):
return lf_enabled
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_archivefile(self):
# archive cache config
from rhodecode import CONFIG
+
_ = self.request.translate
self.load_default_context()
- default_at_path = '/'
- fname = self.request.matchdict['fname']
- subrepos = self.request.GET.get('subrepos') == 'true'
- with_hash = str2bool(self.request.GET.get('with_hash', '1'))
- at_path = self.request.GET.get('at_path') or default_at_path
+
+ subrepos = self.request.GET.get("subrepos") == "true"
+ with_hash = str2bool(self.request.GET.get("with_hash", "1"))
+
+ default_at_path = "/"
+ fname = self.request.matchdict["fname"]
+ at_path = self.request.GET.get("at_path") or default_at_path
if not self.db_repo.enable_downloads:
- return Response(_('Downloads disabled'))
+ return Response(_("Downloads disabled"))
try:
- commit_id, ext, fileformat, content_type = \
- _get_archive_spec(fname)
+ commit_id, ext, file_format, content_type = _get_archive_spec(fname)
except ValueError:
- return Response(_('Unknown archive type for: `{}`').format(
- h.escape(fname)))
+ return Response(_("Unknown archive type for: `{}`").format(h.escape(fname)))
try:
commit = self.rhodecode_vcs_repo.get_commit(commit_id)
except CommitDoesNotExistError:
- return Response(_('Unknown commit_id {}').format(
- h.escape(commit_id)))
+ return Response(_("Unknown commit_id {}").format(h.escape(commit_id)))
except EmptyRepositoryError:
- return Response(_('Empty repository'))
+ return Response(_("Empty repository"))
# we used a ref, or a shorter version, lets redirect client ot use explicit hash
if commit_id != commit.raw_id:
- fname=f'{commit.raw_id}{ext}'
+ fname = f"{commit.raw_id}{ext}"
raise HTTPFound(self.request.current_route_path(fname=fname))
try:
- at_path = commit.get_node(at_path).path or default_at_path
+ at_path = commit.get_node(safe_bytes(at_path)).path or default_at_path
except Exception:
- return Response(_('No node at path {} for this repository').format(h.escape(at_path)))
+ return Response(_("No node at path {} for this repository").format(h.escape(at_path)))
path_sha = get_path_sha(at_path)
# used for cache etc, consistent unique archive name
archive_name_key = get_archive_name(
- self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
- path_sha=path_sha, with_hash=True)
+ self.db_repo.repo_id,
+ self.db_repo_name,
+ commit_sha=commit.short_id,
+ ext=ext,
+ subrepos=subrepos,
+ path_sha=path_sha,
+ with_hash=True,
+ )
if not with_hash:
- path_sha = ''
+ path_sha = ""
# what end client gets served
response_archive_name = get_archive_name(
- self.db_repo.repo_id, self.db_repo_name, commit_sha=commit.short_id, ext=ext, subrepos=subrepos,
- path_sha=path_sha, with_hash=with_hash)
+ self.db_repo.repo_id,
+ self.db_repo_name,
+ commit_sha=commit.short_id,
+ ext=ext,
+ subrepos=subrepos,
+ path_sha=path_sha,
+ with_hash=with_hash,
+ )
# remove extension from our archive directory name
- archive_dir_name = response_archive_name[:-len(ext)]
+ archive_dir_name = response_archive_name[: -len(ext)]
- archive_cache_disable = self.request.GET.get('no_cache')
+ archive_cache_disable = self.request.GET.get("no_cache")
d_cache = get_archival_cache_store(config=CONFIG)
@@ -421,29 +423,38 @@ class RepoFilesView(RepoAppView):
d_cache_conf = get_archival_config(config=CONFIG)
# This is also a cache key, and lock key
- reentrant_lock_key = archive_name_key + '.lock'
+ reentrant_lock_key = archive_name_key + ".lock"
use_cached_archive = False
if not archive_cache_disable and archive_name_key in d_cache:
reader, metadata = d_cache.fetch(archive_name_key)
use_cached_archive = True
- log.debug('Found cached archive as key=%s tag=%s, serving archive from cache reader=%s',
- archive_name_key, metadata, reader.name)
+ log.debug(
+ "Found cached archive as key=%s tag=%s, serving archive from cache reader=%s",
+ archive_name_key,
+ metadata,
+ reader.name,
+ )
else:
reader = None
- log.debug('Archive with key=%s is not yet cached, creating one now...', archive_name_key)
+ log.debug("Archive with key=%s is not yet cached, creating one now...", archive_name_key)
if not reader:
# generate new archive, as previous was not found in the cache
try:
with d_cache.get_lock(reentrant_lock_key):
try:
- commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name,
- kind=fileformat, subrepos=subrepos,
- archive_at_path=at_path, cache_config=d_cache_conf)
+ commit.archive_repo(
+ archive_name_key,
+ archive_dir_name=archive_dir_name,
+ kind=file_format,
+ subrepos=subrepos,
+ archive_at_path=at_path,
+ cache_config=d_cache_conf,
+ )
except ImproperArchiveTypeError:
- return _('Unknown archive type')
+ return _("Unknown archive type")
except ArchiveCacheGenerationLock:
retry_after = round(random.uniform(0.3, 3.0), 1)
@@ -462,7 +473,7 @@ class RepoFilesView(RepoAppView):
reader, metadata = d_cache.fetch(archive_name_key, retry=True, retry_attempts=30)
response = Response(app_iter=archive_iterator(reader))
- response.content_disposition = f'attachment; filename={response_archive_name}'
+ response.content_disposition = f"attachment; filename={response_archive_name}"
response.content_type = str(content_type)
try:
@@ -470,23 +481,25 @@ class RepoFilesView(RepoAppView):
finally:
# store download action
audit_logger.store_web(
- 'repo.archive.download', action_data={
- 'user_agent': self.request.user_agent,
- 'archive_name': archive_name_key,
- 'archive_spec': fname,
- 'archive_cached': use_cached_archive},
+ "repo.archive.download",
+ action_data={
+ "user_agent": self.request.user_agent,
+ "archive_name": archive_name_key,
+ "archive_spec": fname,
+ "archive_cached": use_cached_archive,
+ },
user=self._rhodecode_user,
repo=self.db_repo,
- commit=True
+ commit=True,
)
def _get_file_node(self, commit_id, f_path):
- if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
+ if commit_id not in ["", None, "None", "0" * 12, "0" * 40]:
commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
try:
- node = commit.get_node(f_path)
+ node = commit.get_node(safe_bytes(f_path))
if node.is_dir():
- raise NodeError(f'{node} path is a {type(node)} not a file')
+ raise NodeError(f"{node} path is a {type(node)} not a file")
except NodeDoesNotExistError:
commit = EmptyCommit(
commit_id=commit_id,
@@ -495,46 +508,43 @@ class RepoFilesView(RepoAppView):
alias=commit.repository.alias,
message=commit.message,
author=commit.author,
- date=commit.date)
- node = FileNode(safe_bytes(f_path), b'', commit=commit)
+ date=commit.date,
+ )
+ node = FileNode(safe_bytes(f_path), b"", commit=commit)
else:
- commit = EmptyCommit(
- repo=self.rhodecode_vcs_repo,
- alias=self.rhodecode_vcs_repo.alias)
- node = FileNode(safe_bytes(f_path), b'', commit=commit)
+ commit = EmptyCommit(repo=self.rhodecode_vcs_repo, alias=self.rhodecode_vcs_repo.alias)
+ node = FileNode(safe_bytes(f_path), b"", commit=commit)
return node
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_files_diff(self):
c = self.load_default_context()
f_path = self._get_f_path(self.request.matchdict)
- diff1 = self.request.GET.get('diff1', '')
- diff2 = self.request.GET.get('diff2', '')
+ diff1 = self.request.GET.get("diff1", "")
+ diff2 = self.request.GET.get("diff2", "")
path1, diff1 = parse_path_ref(diff1, default_path=f_path)
- ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
- line_context = self.request.GET.get('context', 3)
+ ignore_whitespace = str2bool(self.request.GET.get("ignorews"))
+ line_context = self.request.GET.get("context", 3)
if not any((diff1, diff2)):
- h.flash(
- 'Need query parameter "diff1" or "diff2" to generate a diff.',
- category='error')
+ h.flash('Need query parameter "diff1" or "diff2" to generate a diff.', category="error")
raise HTTPBadRequest()
- c.action = self.request.GET.get('diff')
- if c.action not in ['download', 'raw']:
+ c.action = self.request.GET.get("diff")
+ if c.action not in ["download", "raw"]:
compare_url = h.route_path(
- 'repo_compare',
+ "repo_compare",
repo_name=self.db_repo_name,
- source_ref_type='rev',
+ source_ref_type="rev",
source_ref=diff1,
target_repo=self.db_repo_name,
- target_ref_type='rev',
+ target_ref_type="rev",
target_ref=diff2,
- _query=dict(f_path=f_path))
+ _query=dict(f_path=f_path),
+ )
# redirect to new view if we render diff
raise HTTPFound(compare_url)
@@ -543,43 +553,34 @@ class RepoFilesView(RepoAppView):
node2 = self._get_file_node(diff2, f_path)
except (RepositoryError, NodeError):
log.exception("Exception while trying to get node from repository")
- raise HTTPFound(
- h.route_path('repo_files', repo_name=self.db_repo_name,
- commit_id='tip', f_path=f_path))
+ raise HTTPFound(h.route_path("repo_files", repo_name=self.db_repo_name, commit_id="tip", f_path=f_path))
- if all(isinstance(node.commit, EmptyCommit)
- for node in (node1, node2)):
+ if all(isinstance(node.commit, EmptyCommit) for node in (node1, node2)):
raise HTTPNotFound()
c.commit_1 = node1.commit
c.commit_2 = node2.commit
- if c.action == 'download':
- _diff = diffs.get_gitdiff(node1, node2,
- ignore_whitespace=ignore_whitespace,
- context=line_context)
+ if c.action == "download":
+ _diff = diffs.get_gitdiff(node1, node2, ignore_whitespace=ignore_whitespace, context=line_context)
# NOTE: this was using diff_format='gitdiff'
- diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
+ diff = diffs.DiffProcessor(_diff, diff_format="newdiff")
response = Response(self.path_filter.get_raw_patch(diff))
- response.content_type = 'text/plain'
- response.content_disposition = (
- f'attachment; filename={f_path}_{diff1}_vs_{diff2}.diff'
- )
+ response.content_type = "text/plain"
+ response.content_disposition = f"attachment; filename={f_path}_{diff1}_vs_{diff2}.diff"
charset = self._get_default_encoding(c)
if charset:
response.charset = charset
return response
- elif c.action == 'raw':
- _diff = diffs.get_gitdiff(node1, node2,
- ignore_whitespace=ignore_whitespace,
- context=line_context)
+ elif c.action == "raw":
+ _diff = diffs.get_gitdiff(node1, node2, ignore_whitespace=ignore_whitespace, context=line_context)
# NOTE: this was using diff_format='gitdiff'
- diff = diffs.DiffProcessor(_diff, diff_format='newdiff')
+ diff = diffs.DiffProcessor(_diff, diff_format="newdiff")
response = Response(self.path_filter.get_raw_patch(diff))
- response.content_type = 'text/plain'
+ response.content_type = "text/plain"
charset = self._get_default_encoding(c)
if charset:
response.charset = charset
@@ -589,31 +590,32 @@ class RepoFilesView(RepoAppView):
raise HTTPNotFound()
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_files_diff_2way_redirect(self):
"""
Kept only to make OLD links work
"""
f_path = self._get_f_path_unchecked(self.request.matchdict)
- diff1 = self.request.GET.get('diff1', '')
- diff2 = self.request.GET.get('diff2', '')
+ diff1 = self.request.GET.get("diff1", "")
+ diff2 = self.request.GET.get("diff2", "")
if not any((diff1, diff2)):
- h.flash(
- 'Need query parameter "diff1" or "diff2" to generate a diff.',
- category='error')
+ h.flash('Need query parameter "diff1" or "diff2" to generate a diff.', category="error")
raise HTTPBadRequest()
compare_url = h.route_path(
- 'repo_compare',
+ "repo_compare",
repo_name=self.db_repo_name,
- source_ref_type='rev',
+ source_ref_type="rev",
source_ref=diff1,
- target_ref_type='rev',
+ target_ref_type="rev",
target_ref=diff2,
- _query=dict(f_path=f_path, diffmode='sideside',
- target_repo=self.db_repo_name,))
+ _query=dict(
+ f_path=f_path,
+ diffmode="sideside",
+ target_repo=self.db_repo_name,
+ ),
+ )
raise HTTPFound(compare_url)
@LoginRequired()
@@ -627,52 +629,51 @@ class RepoFilesView(RepoAppView):
landing_url = h.repo_files_by_ref_url(
c.rhodecode_db_repo.repo_name,
c.rhodecode_db_repo.repo_type,
- f_path='',
+ f_path="",
ref_name=ref_name,
- commit_id='tip',
- query=dict(at=ref_name)
+ commit_id="tip",
+ query=dict(at=ref_name),
)
raise HTTPFound(landing_url)
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_files(self):
c = self.load_default_context()
- view_name = getattr(self.request.matched_route, 'name', None)
+ view_name = getattr(self.request.matched_route, "name", None)
- c.annotate = view_name == 'repo_files:annotated'
+ c.annotate = view_name == "repo_files:annotated"
# default is false, but .rst/.md files later are auto rendered, we can
# overwrite auto rendering by setting this GET flag
- c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False)
+ c.renderer = view_name == "repo_files:rendered" or not self.request.GET.get("no-render", False)
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
c.commit = self._get_commit_or_redirect(commit_id)
- c.branch = self.request.GET.get('branch', None)
+ c.branch = self.request.GET.get("branch", None)
c.f_path = f_path
- at_rev = self.request.GET.get('at')
+ at_rev = self.request.GET.get("at")
# files or dirs
try:
- c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data'])
+ c.file = c.commit.get_node(bytes_path, pre_load=["is_binary", "size", "data"])
c.file_author = True
- c.file_tree = ''
+ c.file_tree = ""
# prev link
try:
prev_commit = c.commit.prev(c.branch)
c.prev_commit = prev_commit
c.url_prev = h.route_path(
- 'repo_files', repo_name=self.db_repo_name,
- commit_id=prev_commit.raw_id, f_path=f_path)
+ "repo_files", repo_name=self.db_repo_name, commit_id=prev_commit.raw_id, f_path=f_path
+ )
if c.branch:
- c.url_prev += '?branch=%s' % c.branch
+ c.url_prev += f"?branch={c.branch}"
except (CommitDoesNotExistError, VCSError):
- c.url_prev = '#'
+ c.url_prev = "#"
c.prev_commit = EmptyCommit()
# next link
@@ -680,110 +681,101 @@ class RepoFilesView(RepoAppView):
next_commit = c.commit.next(c.branch)
c.next_commit = next_commit
c.url_next = h.route_path(
- 'repo_files', repo_name=self.db_repo_name,
- commit_id=next_commit.raw_id, f_path=f_path)
+ "repo_files", repo_name=self.db_repo_name, commit_id=next_commit.raw_id, f_path=f_path
+ )
if c.branch:
- c.url_next += '?branch=%s' % c.branch
+ c.url_next += f"?branch={c.branch}"
except (CommitDoesNotExistError, VCSError):
- c.url_next = '#'
+ c.url_next = "#"
c.next_commit = EmptyCommit()
# load file content
if c.file.is_file():
-
c.lf_node = {}
has_lf_enabled = self._is_lf_enabled(self.db_repo)
if has_lf_enabled:
c.lf_node = c.file.get_largefile_node()
- c.file_source_page = 'true'
+ c.file_source_page = "true"
c.file_last_commit = c.file.last_commit
c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
if not (c.file_size_too_big or c.file.is_binary):
if c.annotate: # annotation has precedence over renderer
- c.annotated_lines = filenode_as_annotated_lines_tokens(
- c.file
- )
+ c.annotated_lines = filenode_as_annotated_lines_tokens(c.file)
else:
- c.renderer = (
- c.renderer and h.renderer_from_filename(c.file.path)
- )
+ c.renderer = c.renderer and h.renderer_from_filename(c.file.path)
if not c.renderer:
c.lines = filenode_as_lines_tokens(c.file)
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
c.on_branch_head = is_head
- branch = c.commit.branch if (
- c.commit.branch and '/' not in c.commit.branch) else None
+ branch = c.commit.branch if (c.commit.branch and "/" not in c.commit.branch) else None
c.branch_or_raw_id = branch or c.commit.raw_id
c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
author = c.file_last_commit.author
- c.authors = [[
- h.email(author),
- h.person(author, 'username_or_name_or_email'),
- 1
- ]]
+ c.authors = [[h.email(author), h.person(author, "username_or_name_or_email"), 1]]
- else: # load tree content at path
- c.file_source_page = 'false'
+ else: # load tree content (dir content) at path
+ c.file_source_page = "false"
c.authors = []
+
+ dir_node = c.file
+ c.file_nodes = dir_node.commit.get_nodes(dir_node.bytes_path, pre_load=dir_node.default_pre_load)
# this loads a simple tree without metadata to speed things up
# later via ajax we call repo_nodetree_full and fetch whole
c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
- c.readme_data, c.readme_file = \
- self._get_readme_data(self.db_repo, c.visual.default_renderer,
- c.commit.raw_id, f_path)
+ c.readme_data, c.readme_file = self._get_readme_data(
+ self.db_repo, c.visual.default_renderer, c.commit.raw_id, bytes_path, nodes=c.file_nodes
+ )
except RepositoryError as e:
- h.flash(h.escape(safe_str(e)), category='error')
+ h.flash(h.escape(safe_str(e)), category="error")
raise HTTPNotFound()
- if self.request.environ.get('HTTP_X_PJAX'):
- html = render('rhodecode:templates/files/files_pjax.mako',
- self._get_template_context(c), self.request)
+ if self.request.environ.get("HTTP_X_PJAX"):
+ html = render("rhodecode:templates/files/files_pjax.mako", self._get_template_context(c), self.request)
else:
- html = render('rhodecode:templates/files/files.mako',
- self._get_template_context(c), self.request)
+ html = render("rhodecode:templates/files/files.mako", self._get_template_context(c), self.request)
return Response(html)
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_files_annotated_previous(self):
self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, bytes_path, bytes_path = self._get_commit_and_path()
commit = self._get_commit_or_redirect(commit_id)
prev_commit_id = commit.raw_id
- line_anchor = self.request.GET.get('line_anchor')
+ line_anchor = self.request.GET.get("line_anchor")
is_file = False
try:
- _file = commit.get_node(f_path)
+ _file = commit.get_node(bytes_path)
is_file = _file.is_file()
except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
pass
if is_file:
- history = commit.get_path_history(f_path)
- prev_commit_id = history[1].raw_id \
- if len(history) > 1 else prev_commit_id
+ history = commit.get_path_history(bytes_path)
+ prev_commit_id = history[1].raw_id if len(history) > 1 else prev_commit_id
prev_url = h.route_path(
- 'repo_files:annotated', repo_name=self.db_repo_name,
- commit_id=prev_commit_id, f_path=f_path,
- _anchor=f'L{line_anchor}')
+ "repo_files:annotated",
+ repo_name=self.db_repo_name,
+ commit_id=prev_commit_id,
+ f_path=bytes_path,
+ _anchor=f"L{line_anchor}",
+ )
raise HTTPFound(prev_url)
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_nodetree_full(self):
"""
Returns rendered html of file tree that contains commit date,
@@ -792,22 +784,22 @@ class RepoFilesView(RepoAppView):
"""
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
commit = self._get_commit_or_redirect(commit_id)
try:
- dir_node = commit.get_node(f_path)
+ dir_node = commit.get_node(bytes_path)
except RepositoryError as e:
- return Response(f'error: {h.escape(safe_str(e))}')
+ return Response(f"error: {h.escape(safe_str(e))}")
if dir_node.is_file():
- return Response('')
+ return Response("")
c.file = dir_node
+ c.file_nodes = dir_node.commit.get_nodes(dir_node.bytes_path, pre_load=dir_node.default_pre_load)
c.commit = commit
- at_rev = self.request.GET.get('at')
+ at_rev = self.request.GET.get("at")
- html = self._get_tree_at_commit(
- c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
+ html = self._get_tree_at_commit(c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
return Response(html)
@@ -816,15 +808,12 @@ class RepoFilesView(RepoAppView):
safe_path = f_name.replace('"', '\\"')
encoded_path = urllib.parse.quote(f_name)
- headers = "attachment; " \
- "filename=\"{}\"; " \
- "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
+ headers = f"attachment; " f'filename="{safe_path}"; ' f"filename*=UTF-8''{encoded_path}"
return header_safe_str(headers)
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_file_raw(self):
"""
Action for show as raw, some mimetypes are "rendered",
@@ -832,25 +821,24 @@ class RepoFilesView(RepoAppView):
"""
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
commit = self._get_commit_or_redirect(commit_id)
- file_node = self._get_filenode_or_redirect(commit, f_path)
+ file_node = self._get_filenode_or_redirect(commit, bytes_path)
raw_mimetype_mapping = {
# map original mimetype to a mimetype used for "show as raw"
# you can also provide a content-disposition to override the
# default "attachment" disposition.
# orig_type: (new_type, new_dispo)
-
# show images inline:
# Do not re-add SVG: it is unsafe and permits XSS attacks. One can
# for example render an SVG with javascript inside or even render
# HTML.
- 'image/x-icon': ('image/x-icon', 'inline'),
- 'image/png': ('image/png', 'inline'),
- 'image/gif': ('image/gif', 'inline'),
- 'image/jpeg': ('image/jpeg', 'inline'),
- 'application/pdf': ('application/pdf', 'inline'),
+ "image/x-icon": ("image/x-icon", "inline"),
+ "image/png": ("image/png", "inline"),
+ "image/gif": ("image/gif", "inline"),
+ "image/jpeg": ("image/jpeg", "inline"),
+ "application/pdf": ("application/pdf", "inline"),
}
mimetype = file_node.mimetype
@@ -860,7 +848,7 @@ class RepoFilesView(RepoAppView):
# we don't know anything special about this, handle it safely
if file_node.is_binary:
# do same as download raw for binary files
- mimetype, disposition = 'application/octet-stream', 'attachment'
+ mimetype, disposition = "application/octet-stream", "attachment"
else:
# do not just use the original mimetype, but force text/plain,
# otherwise it would serve text/html and that might be unsafe.
@@ -869,9 +857,9 @@ class RepoFilesView(RepoAppView):
# binary.This might lead to erroneous text display in some
# cases, but helps in other cases, like with text files
# without extension.
- mimetype, disposition = 'text/plain', 'inline'
+ mimetype, disposition = "text/plain", "inline"
- if disposition == 'attachment':
+ if disposition == "attachment":
disposition = self._get_attachement_headers(f_path)
stream_content = file_node.stream_bytes()
@@ -887,16 +875,15 @@ class RepoFilesView(RepoAppView):
return response
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_file_download(self):
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
commit = self._get_commit_or_redirect(commit_id)
- file_node = self._get_filenode_or_redirect(commit, f_path)
+ file_node = self._get_filenode_or_redirect(commit, bytes_path)
- if self.request.GET.get('lf'):
+ if self.request.GET.get("lf"):
# only if lf get flag is passed, we download this file
# as LFS/Largefile
lf_node = file_node.get_largefile_node()
@@ -919,49 +906,41 @@ class RepoFilesView(RepoAppView):
return response
def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path):
-
- cache_seconds = safe_int(
- rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
+ cache_seconds = rhodecode.ConfigGet().get_int("rc_cache.cache_repo.expiration_time")
cache_on = cache_seconds > 0
log.debug(
- 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`'
- 'with caching: %s[TTL: %ss]' % (
- repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
+ "Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`"
+ "with caching: %s[TTL: %ss]" % (repo_id, commit_id, f_path, cache_on, cache_seconds or 0)
+ )
- cache_namespace_uid = f'repo.{repo_id}'
- region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
+ cache_namespace_uid = f"repo.{repo_id}"
+ region = rc_cache.get_or_create_region("cache_repo", cache_namespace_uid)
@region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path):
- log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
- _repo_id, commit_id, f_path)
+ log.debug("Generating cached nodelist for repo_id:%s, %s, %s", _repo_id, commit_id, f_path)
try:
_d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path)
except (RepositoryError, CommitDoesNotExistError, Exception) as e:
log.exception(safe_str(e))
- h.flash(h.escape(safe_str(e)), category='error')
- raise HTTPFound(h.route_path(
- 'repo_files', repo_name=self.db_repo_name,
- commit_id='tip', f_path='/'))
+ h.flash(h.escape(safe_str(e)), category="error")
+ raise HTTPFound(h.route_path("repo_files", repo_name=self.db_repo_name, commit_id="tip", f_path="/"))
return _d + _f
- result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id,
- commit_id, f_path)
- return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result)
+ result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path)
+ return filter(lambda n: self.path_filter.path_access_allowed(n["name"]), result)
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_nodelist(self):
self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
commit = self._get_commit_or_redirect(commit_id)
- metadata = self._get_nodelist_at_commit(
- self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
- return {'nodes': [x for x in metadata]}
+ metadata = self._get_nodelist_at_commit(self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
+ return {"nodes": [x for x in metadata]}
def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
items = []
@@ -978,7 +957,7 @@ class RepoFilesView(RepoAppView):
# NOTE(dan): old code we used in "diff" mode compare
new_f_path = vcspath.join(name, f_path)
- return f'{new_f_path}@{commit_id}'
+ return f"{new_f_path}@{commit_id}"
def _get_node_history(self, commit_obj, f_path, commits=None):
"""
@@ -996,37 +975,34 @@ class RepoFilesView(RepoAppView):
if commits is None:
pre_load = ["author", "branch"]
try:
- commits = tip.get_path_history(f_path, pre_load=pre_load)
+ commits = tip.get_path_history(safe_bytes(f_path), pre_load=pre_load)
except (NodeDoesNotExistError, CommitError):
# this node is not present at tip!
- commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
+ commits = commit_obj.get_path_history(safe_bytes(f_path), pre_load=pre_load)
history = []
commits_group = ([], _("Changesets"))
for commit in commits:
- branch = ' (%s)' % commit.branch if commit.branch else ''
- n_desc = f'r{commit.idx}:{commit.short_id}{branch}'
- commits_group[0].append((commit.raw_id, n_desc, 'sha'))
+ branch = " (%s)" % commit.branch if commit.branch else ""
+ n_desc = f"r{commit.idx}:{commit.short_id}{branch}"
+ commits_group[0].append((commit.raw_id, n_desc, "sha"))
history.append(commits_group)
symbolic_reference = self._symbolic_reference
- if self.rhodecode_vcs_repo.alias == 'svn':
- adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
- f_path, self.rhodecode_vcs_repo)
+ if self.rhodecode_vcs_repo.alias == "svn":
+ adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(f_path, self.rhodecode_vcs_repo)
if adjusted_f_path != f_path:
log.debug(
- 'Recognized svn tag or branch in file "%s", using svn '
- 'specific symbolic references', f_path)
+ 'Recognized svn tag or branch in file "%s", using svn ' "specific symbolic references", f_path
+ )
f_path = adjusted_f_path
symbolic_reference = self._symbolic_reference_svn
- branches = self._create_references(
- self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
+ branches = self._create_references(self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, "branch")
branches_group = (branches, _("Branches"))
- tags = self._create_references(
- self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
+ tags = self._create_references(self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, "tag")
tags_group = (tags, _("Tags"))
history.append(branches_group)
@@ -1035,14 +1011,13 @@ class RepoFilesView(RepoAppView):
return history, commits
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_file_history(self):
self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
commit = self._get_commit_or_redirect(commit_id)
- file_node = self._get_filenode_or_redirect(commit, f_path)
+ file_node = self._get_filenode_or_redirect(commit, bytes_path)
if file_node.is_file():
file_history, _hist = self._get_node_history(commit, f_path)
@@ -1051,52 +1026,38 @@ class RepoFilesView(RepoAppView):
for section_items, section in file_history:
items = []
for obj_id, obj_text, obj_type in section_items:
- at_rev = ''
- if obj_type in ['branch', 'bookmark', 'tag']:
+ at_rev = ""
+ if obj_type in ["branch", "bookmark", "tag"]:
at_rev = obj_text
- entry = {
- 'id': obj_id,
- 'text': obj_text,
- 'type': obj_type,
- 'at_rev': at_rev
- }
+ entry = {"id": obj_id, "text": obj_text, "type": obj_type, "at_rev": at_rev}
items.append(entry)
- res.append({
- 'text': section,
- 'children': items
- })
+ res.append({"text": section, "children": items})
- data = {
- 'more': False,
- 'results': res
- }
+ data = {"more": False, "results": res}
return data
- log.warning('Cannot fetch history for directory')
+ log.warning("Cannot fetch history for directory")
raise HTTPBadRequest()
@LoginRequired()
- @HasRepoPermissionAnyDecorator(
- 'repository.read', 'repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.read", "repository.write", "repository.admin")
def repo_file_authors(self):
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
commit = self._get_commit_or_redirect(commit_id)
- file_node = self._get_filenode_or_redirect(commit, f_path)
+ file_node = self._get_filenode_or_redirect(commit, bytes_path)
if not file_node.is_file():
raise HTTPBadRequest()
c.file_last_commit = file_node.last_commit
- if self.request.GET.get('annotate') == '1':
+ if self.request.GET.get("annotate") == "1":
# use _hist from annotation if annotation mode is on
commit_ids = {x[1] for x in file_node.annotate}
- _hist = (
- self.rhodecode_vcs_repo.get_commit(commit_id)
- for commit_id in commit_ids)
+ _hist = (self.rhodecode_vcs_repo.get_commit(commit_id) for commit_id in commit_ids)
else:
_f_history, _hist = self._get_node_history(commit, f_path)
c.file_author = False
@@ -1107,8 +1068,8 @@ class RepoFilesView(RepoAppView):
if author not in unique:
unique[commit.author] = [
h.email(author),
- h.person(author, 'username_or_name_or_email'),
- 1 # counter
+ h.person(author, "username_or_name_or_email"),
+ 1, # counter
]
else:
@@ -1120,204 +1081,186 @@ class RepoFilesView(RepoAppView):
return self._get_template_context(c)
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
def repo_files_check_head(self):
self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
- new_path = self.request.POST.get('path')
- operation = self.request.POST.get('operation')
- path_exist = ''
+ new_path = self.request.POST.get("path")
+ operation = self.request.POST.get("operation")
+ path_exist = ""
- if new_path and operation in ['create', 'upload']:
- new_f_path = os.path.join(f_path.lstrip('/'), new_path)
+ if new_path and operation in ["create", "upload"]:
+ new_f_path = os.path.join(f_path.lstrip("/"), new_path)
try:
commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
# NOTE(dan): construct whole path without leading /
- file_node = commit_obj.get_node(new_f_path)
- if file_node is not None:
+ file_node = commit_obj.get_node(safe_bytes(new_f_path))
+ if file_node:
path_exist = new_f_path
- except EmptyRepositoryError:
- pass
- except Exception:
+ except (EmptyRepositoryError, NodeDoesNotExistError):
pass
- return {
- 'branch': _branch_name,
- 'sha': _sha_commit_id,
- 'is_head': is_head,
- 'path_exists': path_exist
- }
+ return {"branch": _branch_name, "sha": _sha_commit_id, "is_head": is_head, "path_exists": path_exist}
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
def repo_files_remove_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
self.forbid_non_head(is_head, f_path)
self.check_branch_permission(_branch_name)
c.commit = self._get_commit_or_redirect(commit_id)
- c.file = self._get_filenode_or_redirect(c.commit, f_path)
+ c.file = self._get_filenode_or_redirect(c.commit, bytes_path)
- c.default_message = _(
- 'Deleted file {} via RhodeCode Enterprise').format(f_path)
+ c.default_message = _("Deleted file {} via RhodeCode Enterprise").format(f_path)
c.f_path = f_path
return self._get_template_context(c)
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
@CSRFRequired()
def repo_files_delete_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
self.forbid_non_head(is_head, f_path)
self.check_branch_permission(_branch_name)
c.commit = self._get_commit_or_redirect(commit_id)
- c.file = self._get_filenode_or_redirect(c.commit, f_path)
+ c.file = self._get_filenode_or_redirect(c.commit, bytes_path)
- c.default_message = _(
- 'Deleted file {} via RhodeCode Enterprise').format(f_path)
+ c.default_message = _("Deleted file {} via RhodeCode Enterprise").format(f_path)
c.f_path = f_path
node_path = f_path
author = self._rhodecode_db_user.full_contact
- message = self.request.POST.get('message') or c.default_message
+ message = self.request.POST.get("message") or c.default_message
try:
- nodes = {
- safe_bytes(node_path): {
- 'content': b''
- }
- }
+ nodes = {safe_bytes(node_path): {"content": b""}}
ScmModel().delete_nodes(
- user=self._rhodecode_db_user.user_id, repo=self.db_repo,
+ user=self._rhodecode_db_user.user_id,
+ repo=self.db_repo,
message=message,
nodes=nodes,
parent_commit=c.commit,
author=author,
)
- h.flash(
- _('Successfully deleted file `{}`').format(
- h.escape(f_path)), category='success')
+ h.flash(_("Successfully deleted file `{}`").format(h.escape(f_path)), category="success")
except Exception:
- log.exception('Error during commit operation')
- h.flash(_('Error occurred during commit'), category='error')
- raise HTTPFound(
- h.route_path('repo_commit', repo_name=self.db_repo_name,
- commit_id='tip'))
+ log.exception("Error during commit operation")
+ h.flash(_("Error occurred during commit"), category="error")
+ raise HTTPFound(h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id="tip"))
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
def repo_files_edit_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
self.forbid_non_head(is_head, f_path, commit_id=commit_id)
self.check_branch_permission(_branch_name, commit_id=commit_id)
c.commit = self._get_commit_or_redirect(commit_id)
- c.file = self._get_filenode_or_redirect(c.commit, f_path)
+ c.file = self._get_filenode_or_redirect(c.commit, bytes_path)
if c.file.is_binary:
files_url = h.route_path(
- 'repo_files',
- repo_name=self.db_repo_name,
- commit_id=c.commit.raw_id, f_path=f_path)
+ "repo_files", repo_name=self.db_repo_name, commit_id=c.commit.raw_id, f_path=f_path
+ )
raise HTTPFound(files_url)
- c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
+ c.default_message = _("Edited file {} via RhodeCode Enterprise").format(f_path)
c.f_path = f_path
return self._get_template_context(c)
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
@CSRFRequired()
def repo_files_update_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
c.commit = self._get_commit_or_redirect(commit_id)
- c.file = self._get_filenode_or_redirect(c.commit, f_path)
+ c.file = self._get_filenode_or_redirect(c.commit, bytes_path)
if c.file.is_binary:
- raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
- commit_id=c.commit.raw_id, f_path=f_path))
+ raise HTTPFound(
+ h.route_path("repo_files", repo_name=self.db_repo_name, commit_id=c.commit.raw_id, f_path=f_path)
+ )
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
self.forbid_non_head(is_head, f_path, commit_id=commit_id)
self.check_branch_permission(_branch_name, commit_id=commit_id)
- c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
+ c.default_message = _("Edited file {} via RhodeCode Enterprise").format(f_path)
c.f_path = f_path
old_content = c.file.str_content
sl = old_content.splitlines(1)
- first_line = sl[0] if sl else ''
+ first_line = sl[0] if sl else ""
r_post = self.request.POST
# line endings: 0 - Unix, 1 - Mac, 2 - DOS
line_ending_mode = detect_mode(first_line, 0)
- content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
+ content = convert_line_endings(r_post.get("content", ""), line_ending_mode)
- message = r_post.get('message') or c.default_message
+ message = r_post.get("message") or c.default_message
org_node_path = c.file.str_path
- filename = r_post['filename']
+ filename = r_post["filename"]
root_path = c.file.dir_path
pure_path = self.create_pure_path(root_path, filename)
node_path = pure_path.as_posix()
- default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
- commit_id=commit_id)
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id=commit_id)
if content == old_content and node_path == org_node_path:
- h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
- category='warning')
+ h.flash(_("No changes detected on {}").format(h.escape(org_node_path)), category="warning")
raise HTTPFound(default_redirect_url)
try:
mapping = {
c.file.bytes_path: {
- 'org_filename': org_node_path,
- 'filename': safe_bytes(node_path),
- 'content': safe_bytes(content),
- 'lexer': '',
- 'op': 'mod',
- 'mode': c.file.mode
+ "org_filename": org_node_path,
+ "filename": safe_bytes(node_path),
+ "content": safe_bytes(content),
+ "lexer": "",
+ "op": "mod",
+ "mode": c.file.mode,
}
}
@@ -1329,28 +1272,26 @@ class RepoFilesView(RepoAppView):
parent_commit=c.commit,
)
- h.flash(_('Successfully committed changes to file `{}`').format(
- h.escape(f_path)), category='success')
- default_redirect_url = h.route_path(
- 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
+ h.flash(_("Successfully committed changes to file `{}`").format(h.escape(f_path)), category="success")
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id=commit.raw_id)
except Exception:
- log.exception('Error occurred during commit')
- h.flash(_('Error occurred during commit'), category='error')
+ log.exception("Error occurred during commit")
+ h.flash(_("Error occurred during commit"), category="error")
raise HTTPFound(default_redirect_url)
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
def repo_files_add_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
# Check if we need to use this page to upload binary
- upload_binary = str2bool(self.request.params.get('upload_binary', False))
+ upload_binary = str2bool(self.request.params.get("upload_binary", False))
c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
if c.commit is None:
@@ -1359,29 +1300,32 @@ class RepoFilesView(RepoAppView):
if self.rhodecode_vcs_repo.is_empty():
# for empty repository we cannot check for current branch, we rely on
# c.commit.branch instead
- _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
+ _branch_name, _sha_commit_id, is_head = c.commit.branch, "", True
else:
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
self.forbid_non_head(is_head, f_path, commit_id=commit_id)
self.check_branch_permission(_branch_name, commit_id=commit_id)
- c.default_message = (_('Added file via RhodeCode Enterprise')) \
- if not upload_binary else (_('Edited file {} via RhodeCode Enterprise').format(f_path))
- c.f_path = f_path.lstrip('/') # ensure not relative path
+ c.default_message = (
+ (_("Added file via RhodeCode Enterprise"))
+ if not upload_binary
+ else (_("Edited file {} via RhodeCode Enterprise").format(f_path))
+ )
+ c.f_path = f_path.lstrip("/") # ensure not relative path
c.replace_binary = upload_binary
return self._get_template_context(c)
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
@CSRFRequired()
def repo_files_create_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
@@ -1391,56 +1335,48 @@ class RepoFilesView(RepoAppView):
# calculate redirect URL
if self.rhodecode_vcs_repo.is_empty():
- default_redirect_url = h.route_path(
- 'repo_summary', repo_name=self.db_repo_name)
+ default_redirect_url = h.route_path("repo_summary", repo_name=self.db_repo_name)
else:
- default_redirect_url = h.route_path(
- 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id="tip")
if self.rhodecode_vcs_repo.is_empty():
# for empty repository we cannot check for current branch, we rely on
# c.commit.branch instead
- _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
+ _branch_name, _sha_commit_id, is_head = c.commit.branch, "", True
else:
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
self.forbid_non_head(is_head, f_path, commit_id=commit_id)
self.check_branch_permission(_branch_name, commit_id=commit_id)
- c.default_message = (_('Added file via RhodeCode Enterprise'))
+ c.default_message = _("Added file via RhodeCode Enterprise")
c.f_path = f_path
r_post = self.request.POST
- message = r_post.get('message') or c.default_message
- filename = r_post.get('filename')
+ message = r_post.get("message") or c.default_message
+ filename = r_post.get("filename")
unix_mode = 0
if not filename:
# If there's no commit, redirect to repo summary
if type(c.commit) is EmptyCommit:
- redirect_url = h.route_path(
- 'repo_summary', repo_name=self.db_repo_name)
+ redirect_url = h.route_path("repo_summary", repo_name=self.db_repo_name)
else:
redirect_url = default_redirect_url
- h.flash(_('No filename specified'), category='warning')
+ h.flash(_("No filename specified"), category="warning")
raise HTTPFound(redirect_url)
root_path = f_path
pure_path = self.create_pure_path(root_path, filename)
- node_path = pure_path.as_posix().lstrip('/')
+ node_path = pure_path.as_posix().lstrip("/")
author = self._rhodecode_db_user.full_contact
- content = convert_line_endings(r_post.get('content', ''), unix_mode)
- nodes = {
- safe_bytes(node_path): {
- 'content': safe_bytes(content)
- }
- }
+ content = convert_line_endings(r_post.get("content", ""), unix_mode)
+ nodes = {safe_bytes(node_path): {"content": safe_bytes(content)}}
try:
-
commit = ScmModel().create_nodes(
user=self._rhodecode_db_user.user_id,
repo=self.db_repo,
@@ -1450,32 +1386,32 @@ class RepoFilesView(RepoAppView):
author=author,
)
- h.flash(_('Successfully committed new file `{}`').format(
- h.escape(node_path)), category='success')
+ h.flash(_("Successfully committed new file `{}`").format(h.escape(node_path)), category="success")
- default_redirect_url = h.route_path(
- 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id=commit.raw_id)
except NonRelativePathError:
- log.exception('Non Relative path found')
- h.flash(_('The location specified must be a relative path and must not '
- 'contain .. in the path'), category='warning')
+ log.exception("Non Relative path found")
+ h.flash(
+ _("The location specified must be a relative path and must not " "contain .. in the path"),
+ category="warning",
+ )
raise HTTPFound(default_redirect_url)
except (NodeError, NodeAlreadyExistsError) as e:
- h.flash(h.escape(safe_str(e)), category='error')
+ h.flash(h.escape(safe_str(e)), category="error")
except Exception:
- log.exception('Error occurred during commit')
- h.flash(_('Error occurred during commit'), category='error')
+ log.exception("Error occurred during commit")
+ h.flash(_("Error occurred during commit"), category="error")
raise HTTPFound(default_redirect_url)
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
@CSRFRequired()
def repo_files_upload_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
@@ -1485,65 +1421,52 @@ class RepoFilesView(RepoAppView):
# calculate redirect URL
if self.rhodecode_vcs_repo.is_empty():
- default_redirect_url = h.route_path(
- 'repo_summary', repo_name=self.db_repo_name)
+ default_redirect_url = h.route_path("repo_summary", repo_name=self.db_repo_name)
else:
- default_redirect_url = h.route_path(
- 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id="tip")
if self.rhodecode_vcs_repo.is_empty():
# for empty repository we cannot check for current branch, we rely on
# c.commit.branch instead
- _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
+ _branch_name, _sha_commit_id, is_head = c.commit.branch, "", True
else:
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
error = self.forbid_non_head(is_head, f_path, json_mode=True)
if error:
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ return {"error": error, "redirect_url": default_redirect_url}
error = self.check_branch_permission(_branch_name, json_mode=True)
if error:
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ return {"error": error, "redirect_url": default_redirect_url}
- c.default_message = (_('Added file via RhodeCode Enterprise'))
+ c.default_message = _("Added file via RhodeCode Enterprise")
c.f_path = f_path
r_post = self.request.POST
message = c.default_message
- user_message = r_post.getall('message')
+ user_message = r_post.getall("message")
if isinstance(user_message, list) and user_message:
# we take the first from duplicated results if it's not empty
message = user_message[0] if user_message[0] else message
nodes = {}
- for file_obj in r_post.getall('files_upload') or []:
+ for file_obj in r_post.getall("files_upload") or []:
content = file_obj.file
filename = file_obj.filename
root_path = f_path
pure_path = self.create_pure_path(root_path, filename)
- node_path = pure_path.as_posix().lstrip('/')
+ node_path = pure_path.as_posix().lstrip("/")
- nodes[safe_bytes(node_path)] = {
- 'content': content
- }
+ nodes[safe_bytes(node_path)] = {"content": content}
if not nodes:
- error = 'missing files'
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ error = "missing files"
+ return {"error": error, "redirect_url": default_redirect_url}
author = self._rhodecode_db_user.full_contact
@@ -1557,54 +1480,40 @@ class RepoFilesView(RepoAppView):
author=author,
)
if len(nodes) == 1:
- flash_message = _('Successfully committed {} new files').format(len(nodes))
+ flash_message = _("Successfully committed {} new files").format(len(nodes))
else:
- flash_message = _('Successfully committed 1 new file')
+ flash_message = _("Successfully committed 1 new file")
- h.flash(flash_message, category='success')
+ h.flash(flash_message, category="success")
- default_redirect_url = h.route_path(
- 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id=commit.raw_id)
except NonRelativePathError:
- log.exception('Non Relative path found')
- error = _('The location specified must be a relative path and must not '
- 'contain .. in the path')
- h.flash(error, category='warning')
+ log.exception("Non Relative path found")
+ error = _("The location specified must be a relative path and must not " "contain .. in the path")
+ h.flash(error, category="warning")
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ return {"error": error, "redirect_url": default_redirect_url}
except (NodeError, NodeAlreadyExistsError) as e:
error = h.escape(e)
- h.flash(error, category='error')
+ h.flash(error, category="error")
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ return {"error": error, "redirect_url": default_redirect_url}
except Exception:
- log.exception('Error occurred during commit')
- error = _('Error occurred during commit')
- h.flash(error, category='error')
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ log.exception("Error occurred during commit")
+ error = _("Error occurred during commit")
+ h.flash(error, category="error")
+ return {"error": error, "redirect_url": default_redirect_url}
- return {
- 'error': None,
- 'redirect_url': default_redirect_url
- }
+ return {"error": None, "redirect_url": default_redirect_url}
@LoginRequired()
- @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+ @HasRepoPermissionAnyDecorator("repository.write", "repository.admin")
@CSRFRequired()
def repo_files_replace_file(self):
_ = self.request.translate
c = self.load_default_context()
- commit_id, f_path = self._get_commit_and_path()
+ commit_id, f_path, bytes_path = self._get_commit_and_path()
self._ensure_not_locked()
@@ -1613,64 +1522,50 @@ class RepoFilesView(RepoAppView):
c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
if self.rhodecode_vcs_repo.is_empty():
- default_redirect_url = h.route_path(
- 'repo_summary', repo_name=self.db_repo_name)
+ default_redirect_url = h.route_path("repo_summary", repo_name=self.db_repo_name)
else:
- default_redirect_url = h.route_path(
- 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id="tip")
if self.rhodecode_vcs_repo.is_empty():
# for empty repository we cannot check for current branch, we rely on
# c.commit.branch instead
- _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
+ _branch_name, _sha_commit_id, is_head = c.commit.branch, "", True
else:
- _branch_name, _sha_commit_id, is_head = \
- self._is_valid_head(commit_id, self.rhodecode_vcs_repo,
- landing_ref=self.db_repo.landing_ref_name)
+ _branch_name, _sha_commit_id, is_head = self._is_valid_head(
+ commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name
+ )
error = self.forbid_non_head(is_head, f_path, json_mode=True)
if error:
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ return {"error": error, "redirect_url": default_redirect_url}
error = self.check_branch_permission(_branch_name, json_mode=True)
if error:
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ return {"error": error, "redirect_url": default_redirect_url}
- c.default_message = (_('Edited file {} via RhodeCode Enterprise').format(f_path))
+ c.default_message = _("Edited file {} via RhodeCode Enterprise").format(f_path)
c.f_path = f_path
r_post = self.request.POST
message = c.default_message
- user_message = r_post.getall('message')
+ user_message = r_post.getall("message")
if isinstance(user_message, list) and user_message:
# we take the first from duplicated results if it's not empty
message = user_message[0] if user_message[0] else message
- data_for_replacement = r_post.getall('files_upload') or []
+ data_for_replacement = r_post.getall("files_upload") or []
if (objects_count := len(data_for_replacement)) > 1:
- return {
- 'error': 'too many files for replacement',
- 'redirect_url': default_redirect_url
- }
+ return {"error": "too many files for replacement", "redirect_url": default_redirect_url}
elif not objects_count:
- return {
- 'error': 'missing files',
- 'redirect_url': default_redirect_url
- }
+ return {"error": "missing files", "redirect_url": default_redirect_url}
content = data_for_replacement[0].file
retrieved_filename = data_for_replacement[0].filename
- if retrieved_filename.split('.')[-1] != f_path.split('.')[-1]:
+ if retrieved_filename.split(".")[-1] != f_path.split(".")[-1]:
return {
- 'error': 'file extension of uploaded file doesn\'t match an original file\'s extension',
- 'redirect_url': default_redirect_url
+ "error": "file extension of uploaded file doesn't match an original file's extension",
+ "redirect_url": default_redirect_url,
}
author = self._rhodecode_db_user.full_contact
@@ -1681,36 +1576,26 @@ class RepoFilesView(RepoAppView):
repo=self.db_repo,
message=message,
node={
- 'content': content,
- 'file_path': f_path.encode(),
+ "content": content,
+ "file_path": f_path.encode(),
},
parent_commit=c.commit,
author=author,
)
- h.flash(_('Successfully committed 1 new file'), category='success')
+ h.flash(_("Successfully committed 1 new file"), category="success")
- default_redirect_url = h.route_path(
- 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
+ default_redirect_url = h.route_path("repo_commit", repo_name=self.db_repo_name, commit_id=commit.raw_id)
except (NodeError, NodeAlreadyExistsError) as e:
error = h.escape(e)
- h.flash(error, category='error')
+ h.flash(error, category="error")
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ return {"error": error, "redirect_url": default_redirect_url}
except Exception:
- log.exception('Error occurred during commit')
- error = _('Error occurred during commit')
- h.flash(error, category='error')
- return {
- 'error': error,
- 'redirect_url': default_redirect_url
- }
+ log.exception("Error occurred during commit")
+ error = _("Error occurred during commit")
+ h.flash(error, category="error")
+ return {"error": error, "redirect_url": default_redirect_url}
- return {
- 'error': None,
- 'redirect_url': default_redirect_url
- }
+ return {"error": None, "redirect_url": default_redirect_url}
diff --git a/rhodecode/apps/repository/views/repo_forks.py b/rhodecode/apps/repository/views/repo_forks.py
--- a/rhodecode/apps/repository/views/repo_forks.py
+++ b/rhodecode/apps/repository/views/repo_forks.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_maintainance.py b/rhodecode/apps/repository/views/repo_maintainance.py
--- a/rhodecode/apps/repository/views/repo_maintainance.py
+++ b/rhodecode/apps/repository/views/repo_maintainance.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_permissions.py b/rhodecode/apps/repository/views/repo_permissions.py
--- a/rhodecode/apps/repository/views/repo_permissions.py
+++ b/rhodecode/apps/repository/views/repo_permissions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py
--- a/rhodecode/apps/repository/views/repo_pull_requests.py
+++ b/rhodecode/apps/repository/views/repo_pull_requests.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -487,7 +487,9 @@ class RepoPullRequestsView(RepoAppView,
c.pull_request_set_reviewers_data_json = ext_json.str_json(c.pull_request_set_reviewers_data_json)
# observers
+ observer_ids = []
for observer_obj, member in pull_request_at_ver.observers():
+ observer_ids.append(observer_obj.user_id)
member_observer = h.reviewer_as_json(
member, reasons=[], mandatory=False,
role=observer_obj.role,
@@ -497,6 +499,7 @@ class RepoPullRequestsView(RepoAppView,
c.pull_request_set_observers_data_json['observers'].append(member_observer)
c.pull_request_set_observers_data_json = ext_json.str_json(c.pull_request_set_observers_data_json)
+ c.status_change_disabled = self._rhodecode_user.user_id in observer_ids
general_comments, inline_comments = \
self.register_comments_vars(c, pull_request_latest, versions)
diff --git a/rhodecode/apps/repository/views/repo_review_rules.py b/rhodecode/apps/repository/views/repo_review_rules.py
--- a/rhodecode/apps/repository/views/repo_review_rules.py
+++ b/rhodecode/apps/repository/views/repo_review_rules.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_settings.py b/rhodecode/apps/repository/views/repo_settings.py
--- a/rhodecode/apps/repository/views/repo_settings.py
+++ b/rhodecode/apps/repository/views/repo_settings.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_settings_advanced.py b/rhodecode/apps/repository/views/repo_settings_advanced.py
--- a/rhodecode/apps/repository/views/repo_settings_advanced.py
+++ b/rhodecode/apps/repository/views/repo_settings_advanced.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_settings_fields.py b/rhodecode/apps/repository/views/repo_settings_fields.py
--- a/rhodecode/apps/repository/views/repo_settings_fields.py
+++ b/rhodecode/apps/repository/views/repo_settings_fields.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_settings_issue_trackers.py b/rhodecode/apps/repository/views/repo_settings_issue_trackers.py
--- a/rhodecode/apps/repository/views/repo_settings_issue_trackers.py
+++ b/rhodecode/apps/repository/views/repo_settings_issue_trackers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_settings_remote.py b/rhodecode/apps/repository/views/repo_settings_remote.py
--- a/rhodecode/apps/repository/views/repo_settings_remote.py
+++ b/rhodecode/apps/repository/views/repo_settings_remote.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_settings_vcs.py b/rhodecode/apps/repository/views/repo_settings_vcs.py
--- a/rhodecode/apps/repository/views/repo_settings_vcs.py
+++ b/rhodecode/apps/repository/views/repo_settings_vcs.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_strip.py b/rhodecode/apps/repository/views/repo_strip.py
--- a/rhodecode/apps/repository/views/repo_strip.py
+++ b/rhodecode/apps/repository/views/repo_strip.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_summary.py b/rhodecode/apps/repository/views/repo_summary.py
--- a/rhodecode/apps/repository/views/repo_summary.py
+++ b/rhodecode/apps/repository/views/repo_summary.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/repository/views/repo_tags.py b/rhodecode/apps/repository/views/repo_tags.py
--- a/rhodecode/apps/repository/views/repo_tags.py
+++ b/rhodecode/apps/repository/views/repo_tags.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/search/__init__.py b/rhodecode/apps/search/__init__.py
--- a/rhodecode/apps/search/__init__.py
+++ b/rhodecode/apps/search/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/search/tests/test_search.py b/rhodecode/apps/search/tests/test_search.py
--- a/rhodecode/apps/search/tests/test_search.py
+++ b/rhodecode/apps/search/tests/test_search.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/search/views.py b/rhodecode/apps/search/views.py
--- a/rhodecode/apps/search/views.py
+++ b/rhodecode/apps/search/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/__init__.py b/rhodecode/apps/ssh_support/__init__.py
--- a/rhodecode/apps/ssh_support/__init__.py
+++ b/rhodecode/apps/ssh_support/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/config_keys.py b/rhodecode/apps/ssh_support/config_keys.py
--- a/rhodecode/apps/ssh_support/config_keys.py
+++ b/rhodecode/apps/ssh_support/config_keys.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/events.py b/rhodecode/apps/ssh_support/events.py
--- a/rhodecode/apps/ssh_support/events.py
+++ b/rhodecode/apps/ssh_support/events.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/lib/__init__.py b/rhodecode/apps/ssh_support/lib/__init__.py
--- a/rhodecode/apps/ssh_support/lib/__init__.py
+++ b/rhodecode/apps/ssh_support/lib/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/lib/backends/__init__.py b/rhodecode/apps/ssh_support/lib/backends/__init__.py
--- a/rhodecode/apps/ssh_support/lib/backends/__init__.py
+++ b/rhodecode/apps/ssh_support/lib/backends/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/lib/backends/base.py b/rhodecode/apps/ssh_support/lib/backends/base.py
--- a/rhodecode/apps/ssh_support/lib/backends/base.py
+++ b/rhodecode/apps/ssh_support/lib/backends/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,7 +20,7 @@ import os
import sys
import logging
-from rhodecode.lib.hook_daemon.base import prepare_callback_daemon
+from rhodecode.lib.hook_daemon.utils import prepare_callback_daemon
from rhodecode.lib.ext_json import sjson as json
from rhodecode.lib.vcs.conf import settings as vcs_settings
from rhodecode.lib.api_utils import call_service_api
@@ -162,9 +162,7 @@ class SshVcsServer(object):
extras = {}
extras.update(tunnel_extras)
- callback_daemon, extras = prepare_callback_daemon(
- extras, protocol=self.hooks_protocol,
- host=vcs_settings.HOOKS_HOST)
+ callback_daemon, extras = prepare_callback_daemon(extras, protocol=self.hooks_protocol)
with callback_daemon:
try:
diff --git a/rhodecode/apps/ssh_support/lib/backends/git.py b/rhodecode/apps/ssh_support/lib/backends/git.py
--- a/rhodecode/apps/ssh_support/lib/backends/git.py
+++ b/rhodecode/apps/ssh_support/lib/backends/git.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/lib/backends/hg.py b/rhodecode/apps/ssh_support/lib/backends/hg.py
--- a/rhodecode/apps/ssh_support/lib/backends/hg.py
+++ b/rhodecode/apps/ssh_support/lib/backends/hg.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/lib/backends/svn.py b/rhodecode/apps/ssh_support/lib/backends/svn.py
--- a/rhodecode/apps/ssh_support/lib/backends/svn.py
+++ b/rhodecode/apps/ssh_support/lib/backends/svn.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -25,7 +25,7 @@ import tempfile
from subprocess import Popen, PIPE
import urllib.parse
-from rhodecode_tools.lib.utils import safe_str
+from rhodecode.lib.str_utils import safe_str
from .base import SshVcsServer
log = logging.getLogger(__name__)
diff --git a/rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py b/rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py
--- a/rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py
+++ b/rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/lib/ssh_wrapper_v2.py b/rhodecode/apps/ssh_support/lib/ssh_wrapper_v2.py
--- a/rhodecode/apps/ssh_support/lib/ssh_wrapper_v2.py
+++ b/rhodecode/apps/ssh_support/lib/ssh_wrapper_v2.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/lib/utils.py b/rhodecode/apps/ssh_support/lib/utils.py
--- a/rhodecode/apps/ssh_support/lib/utils.py
+++ b/rhodecode/apps/ssh_support/lib/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/subscribers.py b/rhodecode/apps/ssh_support/subscribers.py
--- a/rhodecode/apps/ssh_support/subscribers.py
+++ b/rhodecode/apps/ssh_support/subscribers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/tests/__init__.py b/rhodecode/apps/ssh_support/tests/__init__.py
--- a/rhodecode/apps/ssh_support/tests/__init__.py
+++ b/rhodecode/apps/ssh_support/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/tests/conftest.py b/rhodecode/apps/ssh_support/tests/conftest.py
--- a/rhodecode/apps/ssh_support/tests/conftest.py
+++ b/rhodecode/apps/ssh_support/tests/conftest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/tests/test_server_git.py b/rhodecode/apps/ssh_support/tests/test_server_git.py
--- a/rhodecode/apps/ssh_support/tests/test_server_git.py
+++ b/rhodecode/apps/ssh_support/tests/test_server_git.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -33,19 +33,24 @@ class GitServerCreator(object):
'app:main': {
'ssh.executable.git': git_path,
'vcs.hooks.protocol.v2': 'celery',
+ 'app.service_api.host': 'http://localhost',
+ 'app.service_api.token': 'secret4',
+ 'rhodecode.api.url': '/_admin/api',
}
}
repo_name = 'test_git'
repo_mode = 'receive-pack'
user = plain_dummy_user()
- def __init__(self):
- pass
+ def __init__(self, service_api_url, ini_file):
+ self.service_api_url = service_api_url
+ self.ini_file = ini_file
def create(self, **kwargs):
+ self.config_data['app:main']['app.service_api.host'] = self.service_api_url
parameters = {
'store': self.root,
- 'ini_path': '',
+ 'ini_path': self.ini_file,
'user': self.user,
'repo_name': self.repo_name,
'repo_mode': self.repo_mode,
@@ -60,12 +65,30 @@ class GitServerCreator(object):
return server
-@pytest.fixture()
-def git_server(app):
- return GitServerCreator()
+@pytest.fixture(scope='module')
+def git_server(request, module_app, rhodecode_factory, available_port_factory):
+ ini_file = module_app._pyramid_settings['__file__']
+ vcsserver_host = module_app._pyramid_settings['vcs.server']
+
+ store_dir = os.path.dirname(ini_file)
+
+ # start rhodecode for service API
+ rc = rhodecode_factory(
+ request,
+ store_dir=store_dir,
+ port=available_port_factory(),
+ overrides=(
+ {'handler_console': {'level': 'DEBUG'}},
+ {'app:main': {'vcs.server': vcsserver_host}},
+ {'app:main': {'repo_store.path': store_dir}}
+ ))
+
+ service_api_url = f'http://{rc.bind_addr}'
+
+ return GitServerCreator(service_api_url, ini_file)
-class TestGitServer(object):
+class TestGitServer:
def test_command(self, git_server):
server = git_server.create()
@@ -102,14 +125,14 @@ class TestGitServer(object):
assert result is value
def test_run_returns_executes_command(self, git_server):
+ from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper
server = git_server.create()
- from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper
os.environ['SSH_CLIENT'] = '127.0.0.1'
with mock.patch.object(GitTunnelWrapper, 'create_hooks_env') as _patch:
_patch.return_value = 0
with mock.patch.object(GitTunnelWrapper, 'command', return_value='date'):
- exit_code = server.run()
+ exit_code = server.run(tunnel_extras={'config': server.ini_path})
assert exit_code == (0, False)
@@ -135,7 +158,7 @@ class TestGitServer(object):
'action': action,
'ip': '10.10.10.10',
'locked_by': [None, None],
- 'config': '',
+ 'config': git_server.ini_file,
'repo_store': store,
'server_url': None,
'hooks': ['push', 'pull'],
diff --git a/rhodecode/apps/ssh_support/tests/test_server_hg.py b/rhodecode/apps/ssh_support/tests/test_server_hg.py
--- a/rhodecode/apps/ssh_support/tests/test_server_hg.py
+++ b/rhodecode/apps/ssh_support/tests/test_server_hg.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -17,6 +17,7 @@
# and proprietary license terms, please see https://rhodecode.com/licenses/
import os
+
import mock
import pytest
@@ -32,22 +33,27 @@ class MercurialServerCreator(object):
'app:main': {
'ssh.executable.hg': hg_path,
'vcs.hooks.protocol.v2': 'celery',
+ 'app.service_api.host': 'http://localhost',
+ 'app.service_api.token': 'secret4',
+ 'rhodecode.api.url': '/_admin/api',
}
}
repo_name = 'test_hg'
user = plain_dummy_user()
- def __init__(self):
- pass
+ def __init__(self, service_api_url, ini_file):
+ self.service_api_url = service_api_url
+ self.ini_file = ini_file
def create(self, **kwargs):
+ self.config_data['app:main']['app.service_api.host'] = self.service_api_url
parameters = {
'store': self.root,
- 'ini_path': '',
+ 'ini_path': self.ini_file,
'user': self.user,
'repo_name': self.repo_name,
'user_permissions': {
- 'test_hg': 'repository.admin'
+ self.repo_name: 'repository.admin'
},
'settings': self.config_data['app:main'],
'env': plain_dummy_env()
@@ -57,12 +63,30 @@ class MercurialServerCreator(object):
return server
-@pytest.fixture()
-def hg_server(app):
- return MercurialServerCreator()
+@pytest.fixture(scope='module')
+def hg_server(request, module_app, rhodecode_factory, available_port_factory):
+ ini_file = module_app._pyramid_settings['__file__']
+ vcsserver_host = module_app._pyramid_settings['vcs.server']
+
+ store_dir = os.path.dirname(ini_file)
+
+ # start rhodecode for service API
+ rc = rhodecode_factory(
+ request,
+ store_dir=store_dir,
+ port=available_port_factory(),
+ overrides=(
+ {'handler_console': {'level': 'DEBUG'}},
+ {'app:main': {'vcs.server': vcsserver_host}},
+ {'app:main': {'repo_store.path': store_dir}}
+ ))
+
+ service_api_url = f'http://{rc.bind_addr}'
+
+ return MercurialServerCreator(service_api_url, ini_file)
-class TestMercurialServer(object):
+class TestMercurialServer:
def test_command(self, hg_server, tmpdir):
server = hg_server.create()
@@ -107,7 +131,7 @@ class TestMercurialServer(object):
with mock.patch.object(MercurialTunnelWrapper, 'create_hooks_env') as _patch:
_patch.return_value = 0
with mock.patch.object(MercurialTunnelWrapper, 'command', return_value='date'):
- exit_code = server.run()
+ exit_code = server.run(tunnel_extras={'config': server.ini_path})
assert exit_code == (0, False)
diff --git a/rhodecode/apps/ssh_support/tests/test_server_svn.py b/rhodecode/apps/ssh_support/tests/test_server_svn.py
--- a/rhodecode/apps/ssh_support/tests/test_server_svn.py
+++ b/rhodecode/apps/ssh_support/tests/test_server_svn.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -15,7 +15,9 @@
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
+
import os
+
import mock
import pytest
@@ -26,39 +28,62 @@ from rhodecode.apps.ssh_support.tests.co
class SubversionServerCreator(object):
root = '/tmp/repo/path/'
svn_path = '/usr/local/bin/svnserve'
+
config_data = {
'app:main': {
'ssh.executable.svn': svn_path,
'vcs.hooks.protocol.v2': 'celery',
+ 'app.service_api.host': 'http://localhost',
+ 'app.service_api.token': 'secret4',
+ 'rhodecode.api.url': '/_admin/api',
}
}
repo_name = 'test-svn'
user = plain_dummy_user()
- def __init__(self):
- pass
+ def __init__(self, service_api_url, ini_file):
+ self.service_api_url = service_api_url
+ self.ini_file = ini_file
def create(self, **kwargs):
+ self.config_data['app:main']['app.service_api.host'] = self.service_api_url
parameters = {
'store': self.root,
+ 'ini_path': self.ini_file,
+ 'user': self.user,
'repo_name': self.repo_name,
- 'ini_path': '',
- 'user': self.user,
'user_permissions': {
self.repo_name: 'repository.admin'
},
'settings': self.config_data['app:main'],
'env': plain_dummy_env()
}
-
parameters.update(kwargs)
server = SubversionServer(**parameters)
return server
-@pytest.fixture()
-def svn_server(app):
- return SubversionServerCreator()
+@pytest.fixture(scope='module')
+def svn_server(request, module_app, rhodecode_factory, available_port_factory):
+ ini_file = module_app._pyramid_settings['__file__']
+ vcsserver_host = module_app._pyramid_settings['vcs.server']
+
+ store_dir = os.path.dirname(ini_file)
+
+ # start rhodecode for service API
+ rc = rhodecode_factory(
+ request,
+ store_dir=store_dir,
+ port=available_port_factory(),
+ overrides=(
+ {'handler_console': {'level': 'DEBUG'}},
+ {'app:main': {'vcs.server': vcsserver_host}},
+ {'app:main': {'repo_store.path': store_dir}}
+ ))
+
+ service_api_url = f'http://{rc.bind_addr}'
+
+ return SubversionServerCreator(service_api_url, ini_file)
class TestSubversionServer(object):
@@ -168,8 +193,9 @@ class TestSubversionServer(object):
assert repo_name == expected_match
def test_run_returns_executes_command(self, svn_server):
+ from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
+
server = svn_server.create()
- from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
os.environ['SSH_CLIENT'] = '127.0.0.1'
with mock.patch.object(
SubversionTunnelWrapper, 'get_first_client_response',
@@ -184,20 +210,18 @@ class TestSubversionServer(object):
SubversionTunnelWrapper, 'command',
return_value=['date']):
- exit_code = server.run()
+ exit_code = server.run(tunnel_extras={'config': server.ini_path})
# SVN has this differently configured, and we get in our mock env
# None as return code
assert exit_code == (None, False)
def test_run_returns_executes_command_that_cannot_extract_repo_name(self, svn_server):
+ from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
+
server = svn_server.create()
- from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
- with mock.patch.object(
- SubversionTunnelWrapper, 'command',
- return_value=['date']):
- with mock.patch.object(
- SubversionTunnelWrapper, 'get_first_client_response',
+ with mock.patch.object(SubversionTunnelWrapper, 'command', return_value=['date']):
+ with mock.patch.object(SubversionTunnelWrapper, 'get_first_client_response',
return_value=None):
- exit_code = server.run()
+ exit_code = server.run(tunnel_extras={'config': server.ini_path})
assert exit_code == (1, False)
diff --git a/rhodecode/apps/ssh_support/tests/test_ssh_authorized_keys_gen.py b/rhodecode/apps/ssh_support/tests/test_ssh_authorized_keys_gen.py
--- a/rhodecode/apps/ssh_support/tests/test_ssh_authorized_keys_gen.py
+++ b/rhodecode/apps/ssh_support/tests/test_ssh_authorized_keys_gen.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/tests/test_ssh_wrapper.py b/rhodecode/apps/ssh_support/tests/test_ssh_wrapper.py
--- a/rhodecode/apps/ssh_support/tests/test_ssh_wrapper.py
+++ b/rhodecode/apps/ssh_support/tests/test_ssh_wrapper.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/ssh_support/utils.py b/rhodecode/apps/ssh_support/utils.py
--- a/rhodecode/apps/ssh_support/utils.py
+++ b/rhodecode/apps/ssh_support/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/svn_support/__init__.py b/rhodecode/apps/svn_support/__init__.py
--- a/rhodecode/apps/svn_support/__init__.py
+++ b/rhodecode/apps/svn_support/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/svn_support/config_keys.py b/rhodecode/apps/svn_support/config_keys.py
--- a/rhodecode/apps/svn_support/config_keys.py
+++ b/rhodecode/apps/svn_support/config_keys.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/svn_support/events.py b/rhodecode/apps/svn_support/events.py
--- a/rhodecode/apps/svn_support/events.py
+++ b/rhodecode/apps/svn_support/events.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/svn_support/subscribers.py b/rhodecode/apps/svn_support/subscribers.py
--- a/rhodecode/apps/svn_support/subscribers.py
+++ b/rhodecode/apps/svn_support/subscribers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/svn_support/tests/test_mod_dav_svn_config.py b/rhodecode/apps/svn_support/tests/test_mod_dav_svn_config.py
--- a/rhodecode/apps/svn_support/tests/test_mod_dav_svn_config.py
+++ b/rhodecode/apps/svn_support/tests/test_mod_dav_svn_config.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/svn_support/utils.py b/rhodecode/apps/svn_support/utils.py
--- a/rhodecode/apps/svn_support/utils.py
+++ b/rhodecode/apps/svn_support/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_group/__init__.py b/rhodecode/apps/user_group/__init__.py
--- a/rhodecode/apps/user_group/__init__.py
+++ b/rhodecode/apps/user_group/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_group/tests/__init__.py b/rhodecode/apps/user_group/tests/__init__.py
--- a/rhodecode/apps/user_group/tests/__init__.py
+++ b/rhodecode/apps/user_group/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_group/tests/test_user_groups.py b/rhodecode/apps/user_group/tests/test_user_groups.py
--- a/rhodecode/apps/user_group/tests/test_user_groups.py
+++ b/rhodecode/apps/user_group/tests/test_user_groups.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,7 +22,7 @@ from rhodecode.tests import (
TestController, assert_session_flash, TEST_USER_ADMIN_LOGIN)
from rhodecode.model.db import UserGroup
from rhodecode.model.meta import Session
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/user_group/tests/test_user_groups_permissions.py b/rhodecode/apps/user_group/tests/test_user_groups_permissions.py
--- a/rhodecode/apps/user_group/tests/test_user_groups_permissions.py
+++ b/rhodecode/apps/user_group/tests/test_user_groups_permissions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_group/views/__init__.py b/rhodecode/apps/user_group/views/__init__.py
--- a/rhodecode/apps/user_group/views/__init__.py
+++ b/rhodecode/apps/user_group/views/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_group_profile/__init__.py b/rhodecode/apps/user_group_profile/__init__.py
--- a/rhodecode/apps/user_group_profile/__init__.py
+++ b/rhodecode/apps/user_group_profile/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_group_profile/tests/__init__.py b/rhodecode/apps/user_group_profile/tests/__init__.py
--- a/rhodecode/apps/user_group_profile/tests/__init__.py
+++ b/rhodecode/apps/user_group_profile/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_group_profile/tests/test_user_group.py b/rhodecode/apps/user_group_profile/tests/test_user_group.py
--- a/rhodecode/apps/user_group_profile/tests/test_user_group.py
+++ b/rhodecode/apps/user_group_profile/tests/test_user_group.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -18,7 +18,7 @@
from rhodecode.model.user_group import UserGroupModel
from rhodecode.tests import (
TestController, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.routes import route_path
fixture = Fixture()
diff --git a/rhodecode/apps/user_group_profile/views.py b/rhodecode/apps/user_group_profile/views.py
--- a/rhodecode/apps/user_group_profile/views.py
+++ b/rhodecode/apps/user_group_profile/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_profile/__init__.py b/rhodecode/apps/user_profile/__init__.py
--- a/rhodecode/apps/user_profile/__init__.py
+++ b/rhodecode/apps/user_profile/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_profile/tests/__init__.py b/rhodecode/apps/user_profile/tests/__init__.py
--- a/rhodecode/apps/user_profile/tests/__init__.py
+++ b/rhodecode/apps/user_profile/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/apps/user_profile/tests/test_users.py b/rhodecode/apps/user_profile/tests/test_users.py
--- a/rhodecode/apps/user_profile/tests/test_users.py
+++ b/rhodecode/apps/user_profile/tests/test_users.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,7 +22,7 @@ from rhodecode.model.db import User
from rhodecode.tests import (
TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.tests.utils import AssertResponse
from rhodecode.tests.routes import route_path
diff --git a/rhodecode/apps/user_profile/views.py b/rhodecode/apps/user_profile/views.py
--- a/rhodecode/apps/user_profile/views.py
+++ b/rhodecode/apps/user_profile/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/__init__.py b/rhodecode/authentication/__init__.py
--- a/rhodecode/authentication/__init__.py
+++ b/rhodecode/authentication/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/base.py b/rhodecode/authentication/base.py
--- a/rhodecode/authentication/base.py
+++ b/rhodecode/authentication/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/interface.py b/rhodecode/authentication/interface.py
--- a/rhodecode/authentication/interface.py
+++ b/rhodecode/authentication/interface.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/__init__.py b/rhodecode/authentication/plugins/__init__.py
--- a/rhodecode/authentication/plugins/__init__.py
+++ b/rhodecode/authentication/plugins/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/auth_crowd.py b/rhodecode/authentication/plugins/auth_crowd.py
--- a/rhodecode/authentication/plugins/auth_crowd.py
+++ b/rhodecode/authentication/plugins/auth_crowd.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/auth_headers.py b/rhodecode/authentication/plugins/auth_headers.py
--- a/rhodecode/authentication/plugins/auth_headers.py
+++ b/rhodecode/authentication/plugins/auth_headers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/auth_jasig_cas.py b/rhodecode/authentication/plugins/auth_jasig_cas.py
--- a/rhodecode/authentication/plugins/auth_jasig_cas.py
+++ b/rhodecode/authentication/plugins/auth_jasig_cas.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/auth_ldap.py b/rhodecode/authentication/plugins/auth_ldap.py
--- a/rhodecode/authentication/plugins/auth_ldap.py
+++ b/rhodecode/authentication/plugins/auth_ldap.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/auth_pam.py b/rhodecode/authentication/plugins/auth_pam.py
--- a/rhodecode/authentication/plugins/auth_pam.py
+++ b/rhodecode/authentication/plugins/auth_pam.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/auth_rhodecode.py b/rhodecode/authentication/plugins/auth_rhodecode.py
--- a/rhodecode/authentication/plugins/auth_rhodecode.py
+++ b/rhodecode/authentication/plugins/auth_rhodecode.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/plugins/auth_token.py b/rhodecode/authentication/plugins/auth_token.py
--- a/rhodecode/authentication/plugins/auth_token.py
+++ b/rhodecode/authentication/plugins/auth_token.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/registry.py b/rhodecode/authentication/registry.py
--- a/rhodecode/authentication/registry.py
+++ b/rhodecode/authentication/registry.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/routes.py b/rhodecode/authentication/routes.py
--- a/rhodecode/authentication/routes.py
+++ b/rhodecode/authentication/routes.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/schema.py b/rhodecode/authentication/schema.py
--- a/rhodecode/authentication/schema.py
+++ b/rhodecode/authentication/schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/tests/conftest.py b/rhodecode/authentication/tests/conftest.py
--- a/rhodecode/authentication/tests/conftest.py
+++ b/rhodecode/authentication/tests/conftest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/tests/functional/test_settings.py b/rhodecode/authentication/tests/functional/test_settings.py
--- a/rhodecode/authentication/tests/functional/test_settings.py
+++ b/rhodecode/authentication/tests/functional/test_settings.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/authentication/views.py b/rhodecode/authentication/views.py
--- a/rhodecode/authentication/views.py
+++ b/rhodecode/authentication/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/bootstrap.py b/rhodecode/bootstrap.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/bootstrap.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2010-2024 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# 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 Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+# bootstrap data available for tests and setup clean install
+
+TEST_USER_ADMIN_LOGIN = 'test_admin'
+TEST_USER_ADMIN_PASS = 'test12'
+TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
+
+TEST_USER_REGULAR_LOGIN = 'test_regular'
+TEST_USER_REGULAR_PASS = 'test12'
+TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
+
+TEST_USER_REGULAR2_LOGIN = 'test_regular2'
+TEST_USER_REGULAR2_PASS = 'test12'
+TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
+
+HG_REPO = 'vcs_test_hg'
+GIT_REPO = 'vcs_test_git'
+SVN_REPO = 'vcs_test_svn'
+
+NEW_HG_REPO = 'vcs_test_hg_new'
+NEW_GIT_REPO = 'vcs_test_git_new'
+
+HG_FORK = 'vcs_test_hg_fork'
+GIT_FORK = 'vcs_test_git_fork'
diff --git a/rhodecode/config/__init__.py b/rhodecode/config/__init__.py
--- a/rhodecode/config/__init__.py
+++ b/rhodecode/config/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/conf.py b/rhodecode/config/conf.py
--- a/rhodecode/config/conf.py
+++ b/rhodecode/config/conf.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 RhodeCode GmbH
+# Copyright (C) 2013-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/config_maker.py b/rhodecode/config/config_maker.py
--- a/rhodecode/config/config_maker.py
+++ b/rhodecode/config/config_maker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -122,9 +122,12 @@ def sanitize_settings_and_apply_defaults
settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
- settings_maker.make_setting('vcs.git.lfs.storage_location', '/var/opt/rhodecode_repo_store/.cache/git_lfs_store')
+ settings_maker.make_setting('vcs.git.lfs.storage_location',
+ '/var/opt/rhodecode_repo_store/.cache/git_lfs_store',
+ parser='dir:ensured', default_when_empty=True)
settings_maker.make_setting('vcs.hg.largefiles.storage_location',
- '/var/opt/rhodecode_repo_store/.cache/hg_largefiles_store')
+ '/var/opt/rhodecode_repo_store/.cache/hg_largefiles_store',
+ parser='dir:ensured', default_when_empty=True)
settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
@@ -162,7 +165,7 @@ def sanitize_settings_and_apply_defaults
)
# celery
- broker_url = settings_maker.make_setting('celery.broker_url', 'redis://redis:6379/8')
+ broker_url = settings_maker.make_setting('celery.broker_url', 'redis://redis:6379/8', default_when_empty=True)
settings_maker.make_setting('celery.result_backend', broker_url)
settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
diff --git a/rhodecode/config/environment.py b/rhodecode/config/environment.py
--- a/rhodecode/config/environment.py
+++ b/rhodecode/config/environment.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -30,7 +30,7 @@ from rhodecode.lib.vcs import connect_vc
log = logging.getLogger(__name__)
-def propagate_rhodecode_config(global_config, settings, config):
+def propagate_rhodecode_config(global_config, settings, config, full=True):
# Store the settings to make them available to other modules.
settings_merged = global_config.copy()
settings_merged.update(settings)
@@ -40,7 +40,7 @@ def propagate_rhodecode_config(global_co
rhodecode.PYRAMID_SETTINGS = settings_merged
rhodecode.CONFIG = settings_merged
- if 'default_user_id' not in rhodecode.CONFIG:
+ if full and 'default_user_id' not in rhodecode.CONFIG:
rhodecode.CONFIG['default_user_id'] = utils.get_default_user_id()
log.debug('set rhodecode.CONFIG data')
@@ -93,6 +93,7 @@ def load_pyramid_environment(global_conf
# first run, to store data...
propagate_rhodecode_config(global_config, settings, {})
+
if vcs_server_enabled:
connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
else:
diff --git a/rhodecode/config/jsroutes.py b/rhodecode/config/jsroutes.py
--- a/rhodecode/config/jsroutes.py
+++ b/rhodecode/config/jsroutes.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py
--- a/rhodecode/config/middleware.py
+++ b/rhodecode/config/middleware.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -49,7 +49,7 @@ from rhodecode.lib.plugins.utils import
from rhodecode.lib.utils2 import AttributeDict
from rhodecode.lib.exc_tracking import store_exception, format_exc
from rhodecode.subscribers import (
- scan_repositories_if_enabled, write_js_routes_if_enabled,
+ auto_merge_pr_if_needed, scan_repositories_if_enabled, write_js_routes_if_enabled,
write_metadata_if_needed, write_usage_data, import_license_if_present)
from rhodecode.lib.statsd_client import StatsdClient
@@ -101,6 +101,9 @@ def make_pyramid_app(global_config, **se
patches.inspect_getargspec()
patches.repoze_sendmail_lf_fix()
+ # first init, so load_pyramid_enviroment, can access some critical data, like __file__
+ propagate_rhodecode_config(global_config, {}, {}, full=False)
+
load_pyramid_environment(global_config, settings)
# Static file view comes first
@@ -392,6 +395,7 @@ def includeme(config, auth_resources=Non
# Add subscribers.
if load_all:
log.debug('Adding subscribers...')
+ config.add_subscriber(auto_merge_pr_if_needed, rhodecode.events.PullRequestReviewEvent)
config.add_subscriber(scan_repositories_if_enabled,
pyramid.events.ApplicationCreated)
config.add_subscriber(write_metadata_if_needed,
diff --git a/rhodecode/config/patches.py b/rhodecode/config/patches.py
--- a/rhodecode/config/patches.py
+++ b/rhodecode/config/patches.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/rcextensions/__init__.py b/rhodecode/config/rcextensions/__init__.py
--- a/rhodecode/config/rcextensions/__init__.py
+++ b/rhodecode/config/rcextensions/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -17,7 +17,7 @@
# and proprietary license terms, please see https://rhodecode.com/licenses/
"""
-rcextensions module, please edit `hooks.py` to over write hooks logic
+rcextensions module, please edit `hooks.py` to over-write hooks logic
"""
from .hooks import (
diff --git a/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py b/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py
--- a/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py
+++ b/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py
@@ -85,7 +85,7 @@ def _pre_push_hook(*args, **kwargs):
# check files names
if forbidden_files:
- reason = 'File {} is forbidden to be pushed'.format(file_name)
+ reason = f'File {file_name} is forbidden to be pushed'
for forbidden_pattern in forbid_files:
# here we can also filter for operation, e.g if check for only ADDED files
# if operation == 'A':
diff --git a/rhodecode/config/rcextensions/helpers/__init__.py b/rhodecode/config/rcextensions/helpers/__init__.py
--- a/rhodecode/config/rcextensions/helpers/__init__.py
+++ b/rhodecode/config/rcextensions/helpers/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/rcextensions/helpers/extra_fields.py b/rhodecode/config/rcextensions/helpers/extra_fields.py
--- a/rhodecode/config/rcextensions/helpers/extra_fields.py
+++ b/rhodecode/config/rcextensions/helpers/extra_fields.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -55,7 +54,7 @@ def run(*args, **kwargs):
return fields
-class _Undefined(object):
+class _Undefined:
pass
@@ -67,7 +66,7 @@ def get_field(extra_fields_data, key, de
if key not in extra_fields_data:
if isinstance(default, _Undefined):
- raise ValueError('key {} not present in extra_fields'.format(key))
+ raise ValueError(f'key {key} not present in extra_fields')
return default
# NOTE(dan): from metadata we get field_label, field_value, field_desc, field_type
diff --git a/rhodecode/config/rcextensions/helpers/extract_post_commits.py b/rhodecode/config/rcextensions/helpers/extract_post_commits.py
--- a/rhodecode/config/rcextensions/helpers/extract_post_commits.py
+++ b/rhodecode/config/rcextensions/helpers/extract_post_commits.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/rcextensions/helpers/extract_pre_commits.py b/rhodecode/config/rcextensions/helpers/extract_pre_commits.py
--- a/rhodecode/config/rcextensions/helpers/extract_pre_commits.py
+++ b/rhodecode/config/rcextensions/helpers/extract_pre_commits.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -52,7 +51,7 @@ def get_git_commits(repo, refs):
cmd = [
'log',
'--pretty=format:{"commit_id": "%H", "author": "%aN <%aE>", "date": "%ad", "message": "%s"}',
- '{}...{}'.format(old_rev, new_rev)
+ f'{old_rev}...{new_rev}'
]
stdout, stderr = repo.run_git_command(cmd, extra_env=git_env)
@@ -80,12 +79,12 @@ def run(*args, **kwargs):
if vcs_type == 'git':
for rev_data in kwargs['commit_ids']:
- new_environ = dict((k, v) for k, v in rev_data['git_env'])
+ new_environ = {k: v for k, v in rev_data['git_env']}
commits = get_git_commits(vcs_repo, kwargs['commit_ids'])
if vcs_type == 'hg':
for rev_data in kwargs['commit_ids']:
- new_environ = dict((k, v) for k, v in rev_data['hg_env'])
+ new_environ = {k: v for k, v in rev_data['hg_env']}
commits = get_hg_commits(vcs_repo, kwargs['commit_ids'])
return commits
diff --git a/rhodecode/config/rcextensions/helpers/extract_pre_files.py b/rhodecode/config/rcextensions/helpers/extract_pre_files.py
--- a/rhodecode/config/rcextensions/helpers/extract_pre_files.py
+++ b/rhodecode/config/rcextensions/helpers/extract_pre_files.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -133,12 +132,12 @@ def run(*args, **kwargs):
if vcs_type == 'git':
for rev_data in kwargs['commit_ids']:
- new_environ = dict((k, v) for k, v in rev_data['git_env'])
+ new_environ = {k: v for k, v in rev_data['git_env']}
files = get_git_files(repo, vcs_repo, kwargs['commit_ids'])
if vcs_type == 'hg':
for rev_data in kwargs['commit_ids']:
- new_environ = dict((k, v) for k, v in rev_data['hg_env'])
+ new_environ = {k: v for k, v in rev_data['hg_env']}
files = get_hg_files(repo, vcs_repo, kwargs['commit_ids'])
if vcs_type == 'svn':
diff --git a/rhodecode/config/rcextensions/helpers/http_call.py b/rhodecode/config/rcextensions/helpers/http_call.py
--- a/rhodecode/config/rcextensions/helpers/http_call.py
+++ b/rhodecode/config/rcextensions/helpers/http_call.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/rcextensions/hooks.py b/rhodecode/config/rcextensions/hooks.py
--- a/rhodecode/config/rcextensions/hooks.py
+++ b/rhodecode/config/rcextensions/hooks.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/rcextensions/utils.py b/rhodecode/config/rcextensions/utils.py
--- a/rhodecode/config/rcextensions/utils.py
+++ b/rhodecode/config/rcextensions/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -28,7 +28,7 @@ import urllib.error
log = logging.getLogger('rhodecode.' + __name__)
-class HookResponse(object):
+class HookResponse:
def __init__(self, status, output):
self.status = status
self.output = output
@@ -44,6 +44,11 @@ class HookResponse(object):
def __bool__(self):
return self.status == 0
+ def to_json(self):
+ return {'status': self.status, 'output': self.output}
+
+ def __repr__(self):
+ return self.to_json().__repr__()
class DotDict(dict):
@@ -91,8 +96,8 @@ class DotDict(dict):
def __repr__(self):
keys = list(self.keys())
keys.sort()
- args = ', '.join(['%s=%r' % (key, self[key]) for key in keys])
- return '%s(%s)' % (self.__class__.__name__, args)
+ args = ', '.join(['{}={!r}'.format(key, self[key]) for key in keys])
+ return '{}({})'.format(self.__class__.__name__, args)
@staticmethod
def fromDict(d):
@@ -110,7 +115,7 @@ def serialize(x):
def unserialize(x):
if isinstance(x, dict):
- return dict((k, unserialize(v)) for k, v in x.items())
+ return {k: unserialize(v) for k, v in x.items()}
elif isinstance(x, (list, tuple)):
return type(x)(unserialize(v) for v in x)
else:
@@ -161,7 +166,8 @@ def str2bool(_str) -> bool:
string into boolean
:param _str: string value to translate into boolean
- :returns: bool from given string
+ :rtype: boolean
+ :returns: boolean from given string
"""
if _str is None:
return False
diff --git a/rhodecode/config/routing_links.py b/rhodecode/config/routing_links.py
--- a/rhodecode/config/routing_links.py
+++ b/rhodecode/config/routing_links.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -49,22 +48,22 @@ link_config = [
{
"name": "enterprise_docs",
"target": "https://rhodecode.com/r1/enterprise/docs/",
- "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
+ "external_target": "https://docs.rhodecode.com/4.x/rce/index.html",
},
{
"name": "enterprise_log_file_locations",
"target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
- "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
+ "external_target": "https://docs.rhodecode.com/4.x/rce/admin/system-overview.html#log-files",
},
{
"name": "enterprise_issue_tracker_settings",
"target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
- "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
+ "external_target": "https://docs.rhodecode.com/4.x/rce/issue-trackers/issue-trackers.html",
},
{
"name": "enterprise_svn_setup",
"target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
- "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
+ "external_target": "https://docs.rhodecode.com/4.x/rce/admin/svn-http.html",
},
{
"name": "enterprise_license_convert_from_old",
diff --git a/rhodecode/config/settings_maker.py b/rhodecode/config/settings_maker.py
--- a/rhodecode/config/settings_maker.py
+++ b/rhodecode/config/settings_maker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/config/utils.py b/rhodecode/config/utils.py
--- a/rhodecode/config/utils.py
+++ b/rhodecode/config/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -19,6 +19,8 @@
import os
import platform
+from rhodecode.lib.type_utils import str2bool
+
DEFAULT_USER = 'default'
@@ -37,7 +39,6 @@ def configure_vcs(config):
conf.settings.HOOKS_PROTOCOL = config['vcs.hooks.protocol.v2']
conf.settings.HOOKS_HOST = config['vcs.hooks.host']
- conf.settings.DEFAULT_ENCODINGS = config['default_encoding']
conf.settings.ALIASES[:] = config['vcs.backends']
conf.settings.SVN_COMPATIBLE_VERSION = config['vcs.svn.compatible_version']
@@ -48,28 +49,23 @@ def initialize_database(config):
engine = engine_from_config(config, 'sqlalchemy.db1.')
init_model(engine, encryption_key=get_encryption_key(config))
+def initialize_test_environment(settings):
+ skip_test_env = str2bool(os.environ.get('RC_NO_TEST_ENV'))
+ if skip_test_env:
+ return
-def initialize_test_environment(settings, test_env=None):
- if test_env is None:
- test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
+ repo_store_path = os.environ.get('RC_TEST_ENV_REPO_STORE') or settings['repo_store.path']
from rhodecode.lib.utils import (
create_test_directory, create_test_database, create_test_repositories,
create_test_index)
- from rhodecode.tests import TESTS_TMP_PATH
- from rhodecode.lib.vcs.backends.hg import largefiles_store
- from rhodecode.lib.vcs.backends.git import lfs_store
+ create_test_directory(repo_store_path)
+
+ create_test_database(repo_store_path, settings)
# test repos
- if test_env:
- create_test_directory(TESTS_TMP_PATH)
- # large object stores
- create_test_directory(largefiles_store(TESTS_TMP_PATH))
- create_test_directory(lfs_store(TESTS_TMP_PATH))
-
- create_test_database(TESTS_TMP_PATH, settings)
- create_test_repositories(TESTS_TMP_PATH, settings)
- create_test_index(TESTS_TMP_PATH, settings)
+ create_test_repositories(repo_store_path, settings)
+ create_test_index(repo_store_path, settings)
def get_vcs_server_protocol(config):
diff --git a/rhodecode/events/__init__.py b/rhodecode/events/__init__.py
--- a/rhodecode/events/__init__.py
+++ b/rhodecode/events/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -18,13 +18,10 @@
import logging
-from rhodecode.events.base import (
- RhodeCodeIntegrationEvent,
- RhodecodeEvent
-)
+from rhodecode.events.base import RhodeCodeIntegrationEvent, RhodecodeEvent
from rhodecode.events.base import ( # pragma: no cover
- FtsBuild
+ FtsBuild,
)
from rhodecode.events.user import ( # pragma: no cover
@@ -37,11 +34,16 @@ from rhodecode.events.user import ( # p
from rhodecode.events.repo import ( # pragma: no cover
RepoEvent,
- RepoCommitCommentEvent, RepoCommitCommentEditEvent,
- RepoPreCreateEvent, RepoCreateEvent,
- RepoPreDeleteEvent, RepoDeleteEvent,
- RepoPrePushEvent, RepoPushEvent,
- RepoPrePullEvent, RepoPullEvent,
+ RepoCommitCommentEvent,
+ RepoCommitCommentEditEvent,
+ RepoPreCreateEvent,
+ RepoCreateEvent,
+ RepoPreDeleteEvent,
+ RepoDeleteEvent,
+ RepoPrePushEvent,
+ RepoPushEvent,
+ RepoPrePullEvent,
+ RepoPullEvent,
)
from rhodecode.events.repo_group import ( # pragma: no cover
@@ -77,12 +79,13 @@ def trigger(event, registry=None):
from pyramid.threadlocal import get_current_registry
event_name = event.__class__
- log.debug('event %s sent for execution', event_name)
+ log.debug("event %s sent for execution", event_name)
registry = registry or get_current_registry()
registry.notify(event)
- log.debug('event %s triggered using registry %s', event_name, registry)
+ log.debug("event %s triggered using registry %s", event_name, registry)
# Send the events to integrations directly
from rhodecode.integrations import integrations_event_handler
+
if isinstance(event, RhodeCodeIntegrationEvent):
integrations_event_handler(event)
diff --git a/rhodecode/events/base.py b/rhodecode/events/base.py
--- a/rhodecode/events/base.py
+++ b/rhodecode/events/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -26,10 +26,7 @@ from rhodecode.lib.utils2 import Attribu
# this is a user object to be used for events caused by the system (eg. shell)
-SYSTEM_USER = AttributeDict(dict(
- username='__SYSTEM__',
- user_id='__SYSTEM_ID__'
-))
+SYSTEM_USER = AttributeDict(dict(username="__SYSTEM__", user_id="__SYSTEM_ID__"))
log = logging.getLogger(__name__)
@@ -38,16 +35,18 @@ class RhodecodeEvent(object):
"""
Base event class for all RhodeCode events
"""
+
name = "RhodeCodeEvent"
- no_url_set = ''
+ no_url_set = ""
- def __init__(self, request=None, actor=None):
+ def __init__(self, request=None, actor=None, context=None):
self._request = request
self._actor = actor
+ self._context = context
self.utc_timestamp = datetime.datetime.utcnow()
def __repr__(self):
- return '<{}:({})>'.format(self.__class__.__name__, self.name)
+ return f"<{self.__class__.__name__}:(name={self.name}, context={self._context})>"
def get_request(self):
if self._request:
@@ -63,11 +62,11 @@ class RhodecodeEvent(object):
if not self.request:
return
- user = getattr(self.request, 'user', None)
+ user = getattr(self.request, "user", None)
if user:
return user
- api_user = getattr(self.request, 'rpc_user', None)
+ api_user = getattr(self.request, "rpc_user", None)
if api_user:
return api_user
@@ -80,15 +79,17 @@ class RhodecodeEvent(object):
return self._actor
auth_user = self.auth_user
- log.debug('Got integration actor: %s', auth_user)
+ log.debug("Got integration actor: %s", auth_user)
if isinstance(auth_user, AuthUser):
instance = auth_user.get_instance()
# we can't find this DB user...
if not instance:
- return AttributeDict(dict(
- username=auth_user.username,
- user_id=auth_user.user_id,
- ))
+ return AttributeDict(
+ dict(
+ username=auth_user.username,
+ user_id=auth_user.user_id,
+ )
+ )
elif auth_user:
return auth_user
return SYSTEM_USER
@@ -98,29 +99,26 @@ class RhodecodeEvent(object):
auth_user = self.auth_user
if auth_user:
return auth_user.ip_addr
- return ''
+ return ""
@property
def server_url(self):
if self.request:
try:
- return self.request.route_url('home')
+ return self.request.route_url("home")
except Exception:
- log.exception('Failed to fetch URL for server')
+ log.exception("Failed to fetch URL for server")
return self.no_url_set
return self.no_url_set
def as_dict(self):
data = {
- 'name': self.name,
- 'utc_timestamp': self.utc_timestamp,
- 'actor_ip': self.actor_ip,
- 'actor': {
- 'username': self.actor.username,
- 'user_id': self.actor.user_id
- },
- 'server_url': self.server_url
+ "name": self.name,
+ "utc_timestamp": self.utc_timestamp,
+ "actor_ip": self.actor_ip,
+ "actor": {"username": self.actor.username, "user_id": self.actor.user_id},
+ "server_url": self.server_url,
}
return data
@@ -129,13 +127,14 @@ class RhodeCodeIntegrationEvent(Rhodecod
"""
Special subclass for Integration events
"""
- description = ''
+
+ description = ""
class FtsBuild(RhodecodeEvent):
"""
This event will be triggered when FTS Build is triggered
"""
- name = 'fts-build'
- display_name = 'Start FTS Build'
+ name = "fts-build"
+ display_name = "Start FTS Build"
diff --git a/rhodecode/events/interfaces.py b/rhodecode/events/interfaces.py
--- a/rhodecode/events/interfaces.py
+++ b/rhodecode/events/interfaces.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -24,20 +24,23 @@ class IUserRegistered(Interface):
An event type that is emitted whenever a new user registers a user
account.
"""
- user = Attribute('The user object.')
- session = Attribute('The session while processing the register form post.')
+
+ user = Attribute("The user object.")
+ session = Attribute("The session while processing the register form post.")
class IUserPreCreate(Interface):
"""
An event type that is emitted before a new user object is created.
"""
- user_data = Attribute('Data used to create the new user')
+
+ user_data = Attribute("Data used to create the new user")
class IUserPreUpdate(Interface):
"""
An event type that is emitted before a user object is updated.
"""
- user = Attribute('The not yet updated user object')
- user_data = Attribute('Data used to update the user')
+
+ user = Attribute("The not yet updated user object")
+ user_data = Attribute("Data used to update the user")
diff --git a/rhodecode/events/pullrequest.py b/rhodecode/events/pullrequest.py
--- a/rhodecode/events/pullrequest.py
+++ b/rhodecode/events/pullrequest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -19,7 +19,7 @@
import logging
from rhodecode.translation import lazy_ugettext
-from rhodecode.events.repo import (RepoEvent, _commits_as_dict, _issues_as_dict)
+from rhodecode.events.repo import RepoEvent, _commits_as_dict, _issues_as_dict
log = logging.getLogger(__name__)
@@ -30,45 +30,44 @@ class PullRequestEvent(RepoEvent):
:param pullrequest: a :class:`PullRequest` instance
"""
- name = 'pullrequest-event'
- display_name = lazy_ugettext('pullrequest generic event')
- description = lazy_ugettext('All events within a context of a pull request')
+
+ name = "pullrequest-event"
+ display_name = lazy_ugettext("pullrequest generic event")
+ description = lazy_ugettext("All events within a context of a pull request")
- def __init__(self, pullrequest):
- super().__init__(pullrequest.target_repo)
+ def __init__(self, pullrequest, context=None):
+ super().__init__(pullrequest.target_repo, context=context)
self.pullrequest = pullrequest
+ self.context = self._context
def as_dict(self):
from rhodecode.lib.utils2 import md5_safe
from rhodecode.model.pull_request import PullRequestModel
+
data = super().as_dict()
- commits = _commits_as_dict(
- self,
- commit_ids=self.pullrequest.revisions,
- repos=[self.pullrequest.source_repo]
- )
+ commits = _commits_as_dict(self, commit_ids=self.pullrequest.revisions, repos=[self.pullrequest.source_repo])
issues = _issues_as_dict(commits)
# calculate hashes of all commits for unique identifier of commits
# inside that pull request
- commits_hash = md5_safe(':'.join(x.get('raw_id', '') for x in commits))
+ commits_hash = md5_safe(":".join(x.get("raw_id", "") for x in commits))
- data.update({
- 'pullrequest': {
- 'title': self.pullrequest.title,
- 'issues': issues,
- 'pull_request_id': self.pullrequest.pull_request_id,
- 'url': PullRequestModel().get_url(
- self.pullrequest, request=self.request),
- 'permalink_url': PullRequestModel().get_url(
- self.pullrequest, request=self.request, permalink=True),
- 'shadow_url': PullRequestModel().get_shadow_clone_url(
- self.pullrequest, request=self.request),
- 'status': self.pullrequest.calculated_review_status(),
- 'commits_uid': commits_hash,
- 'commits': commits,
+ data.update(
+ {
+ "pullrequest": {
+ "title": self.pullrequest.title,
+ "issues": issues,
+ "pull_request_id": self.pullrequest.pull_request_id,
+ "url": PullRequestModel().get_url(self.pullrequest, request=self.request),
+ "permalink_url": PullRequestModel().get_url(self.pullrequest, request=self.request, permalink=True),
+ "shadow_url": PullRequestModel().get_shadow_clone_url(self.pullrequest, request=self.request),
+ "status": self.pullrequest.calculated_review_status(),
+ "commits_uid": commits_hash,
+ "commits": commits,
+ },
+ "context": self.context,
}
- })
+ )
return data
@@ -77,9 +76,10 @@ class PullRequestCreateEvent(PullRequest
An instance of this class is emitted as an :term:`event` after a pull
request is created.
"""
- name = 'pullrequest-create'
- display_name = lazy_ugettext('pullrequest created')
- description = lazy_ugettext('Event triggered after pull request was created')
+
+ name = "pullrequest-create"
+ display_name = lazy_ugettext("pullrequest created")
+ description = lazy_ugettext("Event triggered after pull request was created")
class PullRequestCloseEvent(PullRequestEvent):
@@ -87,9 +87,10 @@ class PullRequestCloseEvent(PullRequestE
An instance of this class is emitted as an :term:`event` after a pull
request is closed.
"""
- name = 'pullrequest-close'
- display_name = lazy_ugettext('pullrequest closed')
- description = lazy_ugettext('Event triggered after pull request was closed')
+
+ name = "pullrequest-close"
+ display_name = lazy_ugettext("pullrequest closed")
+ description = lazy_ugettext("Event triggered after pull request was closed")
class PullRequestUpdateEvent(PullRequestEvent):
@@ -97,9 +98,10 @@ class PullRequestUpdateEvent(PullRequest
An instance of this class is emitted as an :term:`event` after a pull
request's commits have been updated.
"""
- name = 'pullrequest-update'
- display_name = lazy_ugettext('pullrequest commits updated')
- description = lazy_ugettext('Event triggered after pull requests was updated')
+
+ name = "pullrequest-update"
+ display_name = lazy_ugettext("pullrequest commits updated")
+ description = lazy_ugettext("Event triggered after pull requests was updated")
class PullRequestReviewEvent(PullRequestEvent):
@@ -107,13 +109,13 @@ class PullRequestReviewEvent(PullRequest
An instance of this class is emitted as an :term:`event` after a pull
request review has changed. A status defines new status of review.
"""
- name = 'pullrequest-review'
- display_name = lazy_ugettext('pullrequest review changed')
- description = lazy_ugettext('Event triggered after a review status of a '
- 'pull requests has changed to other.')
- def __init__(self, pullrequest, status):
- super().__init__(pullrequest)
+ name = "pullrequest-review"
+ display_name = lazy_ugettext("pullrequest review changed")
+ description = lazy_ugettext("Event triggered after a review status of a pull requests has changed to other.")
+
+ def __init__(self, pullrequest, status, context=None):
+ super().__init__(pullrequest, context=context)
self.status = status
@@ -122,10 +124,10 @@ class PullRequestMergeEvent(PullRequestE
An instance of this class is emitted as an :term:`event` after a pull
request is merged.
"""
- name = 'pullrequest-merge'
- display_name = lazy_ugettext('pullrequest merged')
- description = lazy_ugettext('Event triggered after a successful merge operation '
- 'was executed on a pull request')
+
+ name = "pullrequest-merge"
+ display_name = lazy_ugettext("pullrequest merged")
+ description = lazy_ugettext("Event triggered after a successful merge operation was executed on a pull request")
class PullRequestCommentEvent(PullRequestEvent):
@@ -133,37 +135,39 @@ class PullRequestCommentEvent(PullReques
An instance of this class is emitted as an :term:`event` after a pull
request comment is created.
"""
- name = 'pullrequest-comment'
- display_name = lazy_ugettext('pullrequest commented')
- description = lazy_ugettext('Event triggered after a comment was made on a code '
- 'in the pull request')
- def __init__(self, pullrequest, comment):
- super().__init__(pullrequest)
+ name = "pullrequest-comment"
+ display_name = lazy_ugettext("pullrequest commented")
+ description = lazy_ugettext("Event triggered after a comment was made on a code in the pull request")
+
+ def __init__(self, pullrequest, comment, context=None):
+ super().__init__(pullrequest, context=context)
self.comment = comment
def as_dict(self):
from rhodecode.model.comment import CommentsModel
+
data = super().as_dict()
status = None
if self.comment.status_change:
status = self.comment.review_status
- data.update({
- 'comment': {
- 'status': status,
- 'text': self.comment.text,
- 'type': self.comment.comment_type,
- 'file': self.comment.f_path,
- 'line': self.comment.line_no,
- 'version': self.comment.last_version,
- 'url': CommentsModel().get_url(
- self.comment, request=self.request),
- 'permalink_url': CommentsModel().get_url(
- self.comment, request=self.request, permalink=True),
+ data.update(
+ {
+ "comment": {
+ "status": status,
+ "text": self.comment.text,
+ "type": self.comment.comment_type,
+ "file": self.comment.f_path,
+ "line": self.comment.line_no,
+ "version": self.comment.last_version,
+ "url": CommentsModel().get_url(self.comment, request=self.request),
+ "permalink_url": CommentsModel().get_url(self.comment, request=self.request, permalink=True),
+ },
+ "context": self.context,
}
- })
+ )
return data
@@ -172,10 +176,10 @@ class PullRequestCommentEditEvent(PullRe
An instance of this class is emitted as an :term:`event` after a pull
request comment is edited.
"""
- name = 'pullrequest-comment-edit'
- display_name = lazy_ugettext('pullrequest comment edited')
- description = lazy_ugettext('Event triggered after a comment was edited on a code '
- 'in the pull request')
+
+ name = "pullrequest-comment-edit"
+ display_name = lazy_ugettext("pullrequest comment edited")
+ description = lazy_ugettext("Event triggered after a comment was edited on a code in the pull request")
def __init__(self, pullrequest, comment):
super().__init__(pullrequest)
@@ -183,24 +187,26 @@ class PullRequestCommentEditEvent(PullRe
def as_dict(self):
from rhodecode.model.comment import CommentsModel
+
data = super().as_dict()
status = None
if self.comment.status_change:
status = self.comment.review_status
- data.update({
- 'comment': {
- 'status': status,
- 'text': self.comment.text,
- 'type': self.comment.comment_type,
- 'file': self.comment.f_path,
- 'line': self.comment.line_no,
- 'version': self.comment.last_version,
- 'url': CommentsModel().get_url(
- self.comment, request=self.request),
- 'permalink_url': CommentsModel().get_url(
- self.comment, request=self.request, permalink=True),
+ data.update(
+ {
+ "comment": {
+ "status": status,
+ "text": self.comment.text,
+ "type": self.comment.comment_type,
+ "file": self.comment.f_path,
+ "line": self.comment.line_no,
+ "version": self.comment.last_version,
+ "url": CommentsModel().get_url(self.comment, request=self.request),
+ "permalink_url": CommentsModel().get_url(self.comment, request=self.request, permalink=True),
+ },
+ "context": self.context
}
- })
+ )
return data
diff --git a/rhodecode/events/repo.py b/rhodecode/events/repo.py
--- a/rhodecode/events/repo.py
+++ b/rhodecode/events/repo.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -37,12 +37,11 @@ def _commits_as_dict(event, commit_ids,
:param repos: a list of repos to check
"""
from rhodecode.lib.utils2 import extract_mentioned_users
- from rhodecode.lib.helpers import (
- urlify_commit_message, process_patterns, chop_at_smart)
+ from rhodecode.lib.helpers import urlify_commit_message, process_patterns, chop_at_smart
from rhodecode.model.repo import RepoModel
if not repos:
- raise Exception('no repo defined')
+ raise Exception("no repo defined")
if not isinstance(repos, (tuple, list)):
repos = [repos]
@@ -63,37 +62,31 @@ def _commits_as_dict(event, commit_ids,
try:
# use copy of needed_commits since we modify it while iterating
for commit_id in list(needed_commits):
- if commit_id.startswith('tag=>'):
+ if commit_id.startswith("tag=>"):
raw_id = commit_id[5:]
cs_data = {
- 'raw_id': commit_id, 'short_id': commit_id,
- 'branch': None,
- 'git_ref_change': 'tag_add',
- 'message': f'Added new tag {raw_id}',
- 'author': event.actor.full_contact,
- 'date': datetime.datetime.now(),
- 'refs': {
- 'branches': [],
- 'bookmarks': [],
- 'tags': []
- }
+ "raw_id": commit_id,
+ "short_id": commit_id,
+ "branch": None,
+ "git_ref_change": "tag_add",
+ "message": f"Added new tag {raw_id}",
+ "author": event.actor.full_contact,
+ "date": datetime.datetime.now(),
+ "refs": {"branches": [], "bookmarks": [], "tags": []},
}
commits.append(cs_data)
- elif commit_id.startswith('delete_branch=>'):
+ elif commit_id.startswith("delete_branch=>"):
raw_id = commit_id[15:]
cs_data = {
- 'raw_id': commit_id, 'short_id': commit_id,
- 'branch': None,
- 'git_ref_change': 'branch_delete',
- 'message': f'Deleted branch {raw_id}',
- 'author': event.actor.full_contact,
- 'date': datetime.datetime.now(),
- 'refs': {
- 'branches': [],
- 'bookmarks': [],
- 'tags': []
- }
+ "raw_id": commit_id,
+ "short_id": commit_id,
+ "branch": None,
+ "git_ref_change": "branch_delete",
+ "message": f"Deleted branch {raw_id}",
+ "author": event.actor.full_contact,
+ "date": datetime.datetime.now(),
+ "refs": {"branches": [], "bookmarks": [], "tags": []},
}
commits.append(cs_data)
@@ -104,50 +97,45 @@ def _commits_as_dict(event, commit_ids,
continue # maybe its in next repo
cs_data = cs.__json__()
- cs_data['refs'] = cs._get_refs()
+ cs_data["refs"] = cs._get_refs()
- cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
- cs_data['reviewers'] = reviewers
- cs_data['url'] = RepoModel().get_commit_url(
- repo, cs_data['raw_id'], request=event.request)
- cs_data['permalink_url'] = RepoModel().get_commit_url(
- repo, cs_data['raw_id'], request=event.request,
- permalink=True)
- urlified_message, issues_data, errors = process_patterns(
- cs_data['message'], repo.repo_name)
- cs_data['issues'] = issues_data
- cs_data['message_html'] = urlify_commit_message(
- cs_data['message'], repo.repo_name)
- cs_data['message_html_title'] = chop_at_smart(
- cs_data['message'], '\n', suffix_if_chopped='...')
+ cs_data["mentions"] = extract_mentioned_users(cs_data["message"])
+ cs_data["reviewers"] = reviewers
+ cs_data["url"] = RepoModel().get_commit_url(repo, cs_data["raw_id"], request=event.request)
+ cs_data["permalink_url"] = RepoModel().get_commit_url(
+ repo, cs_data["raw_id"], request=event.request, permalink=True
+ )
+ urlified_message, issues_data, errors = process_patterns(cs_data["message"], repo.repo_name)
+ cs_data["issues"] = issues_data
+ cs_data["message_html"] = urlify_commit_message(cs_data["message"], repo.repo_name)
+ cs_data["message_html_title"] = chop_at_smart(cs_data["message"], "\n", suffix_if_chopped="...")
commits.append(cs_data)
needed_commits.remove(commit_id)
except Exception:
- log.exception('Failed to extract commits data')
+ log.exception("Failed to extract commits data")
# we don't send any commits when crash happens, only full list
# matters we short circuit then.
return []
# we failed to remove all needed_commits from all repositories
if needed_commits:
- raise ValueError(f'Unexpectedly not found {needed_commits} in all available repos {repos}')
+ raise ValueError(f"Unexpectedly not found {needed_commits} in all available repos {repos}")
- missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
+ missing_commits = set(commit_ids) - set(c["raw_id"] for c in commits)
if missing_commits:
- log.error('Inconsistent repository state. '
- 'Missing commits: %s', ', '.join(missing_commits))
+ log.error("Inconsistent repository state. " "Missing commits: %s", ", ".join(missing_commits))
return commits
def _issues_as_dict(commits):
- """ Helper function to serialize issues from commits """
+ """Helper function to serialize issues from commits"""
issues = {}
for commit in commits:
- for issue in commit['issues']:
- issues[issue['id']] = issue
+ for issue in commit["issues"]:
+ issues[issue["id"]] = issue
return issues
@@ -156,33 +144,36 @@ class RepoEvent(RhodeCodeIntegrationEven
Base class for events acting on a repository.
"""
- def __init__(self, repo, actor=None):
+ def __init__(self, repo, actor=None, context=None):
"""
:param repo: a :class:`Repository` instance
"""
- super().__init__(actor=actor)
+ super().__init__(actor=actor, context=context)
self.repo = repo
+ self.context = self._context
def as_dict(self):
from rhodecode.model.repo import RepoModel
+
data = super().as_dict()
extra_fields = collections.OrderedDict()
for field in self.repo.extra_fields:
extra_fields[field.field_key] = field.field_value
- data.update({
- 'repo': {
- 'repo_id': self.repo.repo_id,
- 'repo_name': self.repo.repo_name,
- 'repo_type': self.repo.repo_type,
- 'url': RepoModel().get_url(
- self.repo, request=self.request),
- 'permalink_url': RepoModel().get_url(
- self.repo, request=self.request, permalink=True),
- 'extra_fields': extra_fields
+ data.update(
+ {
+ "repo": {
+ "repo_id": self.repo.repo_id,
+ "repo_name": self.repo.repo_name,
+ "repo_type": self.repo.repo_type,
+ "url": RepoModel().get_url(self.repo, request=self.request),
+ "permalink_url": RepoModel().get_url(self.repo, request=self.request, permalink=True),
+ "extra_fields": extra_fields,
+ },
+ "context": self.context,
}
- })
+ )
return data
@@ -192,32 +183,32 @@ class RepoCommitCommentEvent(RepoEvent):
on repository commit.
"""
- name = 'repo-commit-comment'
- display_name = lazy_ugettext('repository commit comment')
- description = lazy_ugettext('Event triggered after a comment was made '
- 'on commit inside a repository')
+ name = "repo-commit-comment"
+ display_name = lazy_ugettext("repository commit comment")
+ description = lazy_ugettext("Event triggered after a comment was made on commit inside a repository")
- def __init__(self, repo, commit, comment):
- super().__init__(repo)
+ def __init__(self, repo, commit, comment, context=None):
+ super().__init__(repo, context=context)
self.commit = commit
self.comment = comment
def as_dict(self):
data = super().as_dict()
- data['commit'] = {
- 'commit_id': self.commit.raw_id,
- 'commit_message': self.commit.message,
- 'commit_branch': self.commit.branch,
+ data["commit"] = {
+ "commit_id": self.commit.raw_id,
+ "commit_message": self.commit.message,
+ "commit_branch": self.commit.branch,
}
- data['comment'] = {
- 'comment_id': self.comment.comment_id,
- 'comment_text': self.comment.text,
- 'comment_type': self.comment.comment_type,
- 'comment_f_path': self.comment.f_path,
- 'comment_line_no': self.comment.line_no,
- 'comment_version': self.comment.last_version,
+ data["comment"] = {
+ "comment_id": self.comment.comment_id,
+ "comment_text": self.comment.text,
+ "comment_type": self.comment.comment_type,
+ "comment_f_path": self.comment.f_path,
+ "comment_line_no": self.comment.line_no,
+ "comment_version": self.comment.last_version,
}
+ data["contex"] = self.context
return data
@@ -227,32 +218,32 @@ class RepoCommitCommentEditEvent(RepoEve
on repository commit.
"""
- name = 'repo-commit-edit-comment'
- display_name = lazy_ugettext('repository commit edit comment')
- description = lazy_ugettext('Event triggered after a comment was edited '
- 'on commit inside a repository')
+ name = "repo-commit-edit-comment"
+ display_name = lazy_ugettext("repository commit edit comment")
+ description = lazy_ugettext("Event triggered after a comment was edited on commit inside a repository")
- def __init__(self, repo, commit, comment):
- super().__init__(repo)
+ def __init__(self, repo, commit, comment, context=None):
+ super().__init__(repo, context=context)
self.commit = commit
self.comment = comment
def as_dict(self):
data = super().as_dict()
- data['commit'] = {
- 'commit_id': self.commit.raw_id,
- 'commit_message': self.commit.message,
- 'commit_branch': self.commit.branch,
+ data["commit"] = {
+ "commit_id": self.commit.raw_id,
+ "commit_message": self.commit.message,
+ "commit_branch": self.commit.branch,
}
- data['comment'] = {
- 'comment_id': self.comment.comment_id,
- 'comment_text': self.comment.text,
- 'comment_type': self.comment.comment_type,
- 'comment_f_path': self.comment.f_path,
- 'comment_line_no': self.comment.line_no,
- 'comment_version': self.comment.last_version,
+ data["comment"] = {
+ "comment_id": self.comment.comment_id,
+ "comment_text": self.comment.text,
+ "comment_type": self.comment.comment_type,
+ "comment_f_path": self.comment.f_path,
+ "comment_line_no": self.comment.line_no,
+ "comment_version": self.comment.last_version,
}
+ data["context"] = "context"
return data
@@ -261,9 +252,10 @@ class RepoPreCreateEvent(RepoEvent):
An instance of this class is emitted as an :term:`event` before a repo is
created.
"""
- name = 'repo-pre-create'
- display_name = lazy_ugettext('repository pre create')
- description = lazy_ugettext('Event triggered before repository is created')
+
+ name = "repo-pre-create"
+ display_name = lazy_ugettext("repository pre create")
+ description = lazy_ugettext("Event triggered before repository is created")
class RepoCreateEvent(RepoEvent):
@@ -271,9 +263,10 @@ class RepoCreateEvent(RepoEvent):
An instance of this class is emitted as an :term:`event` whenever a repo is
created.
"""
- name = 'repo-create'
- display_name = lazy_ugettext('repository created')
- description = lazy_ugettext('Event triggered after repository was created')
+
+ name = "repo-create"
+ display_name = lazy_ugettext("repository created")
+ description = lazy_ugettext("Event triggered after repository was created")
class RepoPreDeleteEvent(RepoEvent):
@@ -281,9 +274,10 @@ class RepoPreDeleteEvent(RepoEvent):
An instance of this class is emitted as an :term:`event` whenever a repo is
created.
"""
- name = 'repo-pre-delete'
- display_name = lazy_ugettext('repository pre delete')
- description = lazy_ugettext('Event triggered before a repository is deleted')
+
+ name = "repo-pre-delete"
+ display_name = lazy_ugettext("repository pre delete")
+ description = lazy_ugettext("Event triggered before a repository is deleted")
class RepoDeleteEvent(RepoEvent):
@@ -291,43 +285,45 @@ class RepoDeleteEvent(RepoEvent):
An instance of this class is emitted as an :term:`event` whenever a repo is
created.
"""
- name = 'repo-delete'
- display_name = lazy_ugettext('repository deleted')
- description = lazy_ugettext('Event triggered after repository was deleted')
+
+ name = "repo-delete"
+ display_name = lazy_ugettext("repository deleted")
+ description = lazy_ugettext("Event triggered after repository was deleted")
class RepoVCSEvent(RepoEvent):
"""
Base class for events triggered by the VCS
"""
- name = ''
- display_name = 'generic_vcs_event'
- def __init__(self, repo_name, extras):
+ name = ""
+ display_name = "generic_vcs_event"
+
+ def __init__(self, repo_name, extras, context=None):
self.repo = Repository.get_by_repo_name(repo_name)
if not self.repo:
- raise Exception(f'repo by this name {repo_name} does not exist')
+ raise Exception(f"repo by this name {repo_name} does not exist")
self.extras = extras
- super().__init__(self.repo)
+ super().__init__(self.repo, context=context)
@property
def actor(self):
- if self.extras.get('username'):
- return User.get_by_username(self.extras['username'])
+ if self.extras.get("username"):
+ return User.get_by_username(self.extras["username"])
@property
def actor_ip(self):
- if self.extras.get('ip'):
- return self.extras['ip']
+ if self.extras.get("ip"):
+ return self.extras["ip"]
@property
def server_url(self):
- if self.extras.get('server_url'):
- return self.extras['server_url']
+ if self.extras.get("server_url"):
+ return self.extras["server_url"]
@property
def request(self):
- return self.extras.get('request') or self.get_request()
+ return self.extras.get("request") or self.get_request()
class RepoPrePullEvent(RepoVCSEvent):
@@ -335,9 +331,10 @@ class RepoPrePullEvent(RepoVCSEvent):
An instance of this class is emitted as an :term:`event` before commits
are pulled from a repo.
"""
- name = 'repo-pre-pull'
- display_name = lazy_ugettext('repository pre pull')
- description = lazy_ugettext('Event triggered before repository code is pulled')
+
+ name = "repo-pre-pull"
+ display_name = lazy_ugettext("repository pre pull")
+ description = lazy_ugettext("Event triggered before repository code is pulled")
class RepoPullEvent(RepoVCSEvent):
@@ -345,9 +342,10 @@ class RepoPullEvent(RepoVCSEvent):
An instance of this class is emitted as an :term:`event` after commits
are pulled from a repo.
"""
- name = 'repo-pull'
- display_name = lazy_ugettext('repository pull')
- description = lazy_ugettext('Event triggered after repository code was pulled')
+
+ name = "repo-pull"
+ display_name = lazy_ugettext("repository pull")
+ description = lazy_ugettext("Event triggered after repository code was pulled")
class RepoPrePushEvent(RepoVCSEvent):
@@ -355,10 +353,10 @@ class RepoPrePushEvent(RepoVCSEvent):
An instance of this class is emitted as an :term:`event` before commits
are pushed to a repo.
"""
- name = 'repo-pre-push'
- display_name = lazy_ugettext('repository pre push')
- description = lazy_ugettext('Event triggered before the code is '
- 'pushed to a repository')
+
+ name = "repo-pre-push"
+ display_name = lazy_ugettext("repository pre push")
+ description = lazy_ugettext("Event triggered before the code is pushed to a repository")
class RepoPushEvent(RepoVCSEvent):
@@ -368,13 +366,13 @@ class RepoPushEvent(RepoVCSEvent):
:param extras: (optional) dict of data from proxied VCS actions
"""
- name = 'repo-push'
- display_name = lazy_ugettext('repository push')
- description = lazy_ugettext('Event triggered after the code was '
- 'pushed to a repository')
- def __init__(self, repo_name, pushed_commit_ids, extras):
- super().__init__(repo_name, extras)
+ name = "repo-push"
+ display_name = lazy_ugettext("repository push")
+ description = lazy_ugettext("Event triggered after the code was pushed to a repository")
+
+ def __init__(self, repo_name, pushed_commit_ids, extras, context=None):
+ super().__init__(repo_name, extras, context=context)
self.pushed_commit_ids = pushed_commit_ids
self.new_refs = extras.new_refs
@@ -382,63 +380,48 @@ class RepoPushEvent(RepoVCSEvent):
data = super().as_dict()
def branch_url(branch_name):
- return '{}/changelog?branch={}'.format(
- data['repo']['url'], branch_name)
+ return "{}/changelog?branch={}".format(data["repo"]["url"], branch_name)
def tag_url(tag_name):
- return '{}/files/{}/'.format(
- data['repo']['url'], tag_name)
+ return "{}/files/{}/".format(data["repo"]["url"], tag_name)
- commits = _commits_as_dict(
- self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
+ commits = _commits_as_dict(self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
last_branch = None
for commit in reversed(commits):
- commit['branch'] = commit['branch'] or last_branch
- last_branch = commit['branch']
+ commit["branch"] = commit["branch"] or last_branch
+ last_branch = commit["branch"]
issues = _issues_as_dict(commits)
branches = set()
tags = set()
for commit in commits:
- if commit['refs']['tags']:
- for tag in commit['refs']['tags']:
+ if commit["refs"]["tags"]:
+ for tag in commit["refs"]["tags"]:
tags.add(tag)
- if commit['branch']:
- branches.add(commit['branch'])
+ if commit["branch"]:
+ branches.add(commit["branch"])
# maybe we have branches in new_refs ?
try:
- branches = branches.union(set(self.new_refs['branches']))
+ branches = branches.union(set(self.new_refs["branches"]))
except Exception:
pass
- branches = [
- {
- 'name': branch,
- 'url': branch_url(branch)
- }
- for branch in branches
- ]
+ branches = [{"name": branch, "url": branch_url(branch)} for branch in branches]
# maybe we have branches in new_refs ?
try:
- tags = tags.union(set(self.new_refs['tags']))
+ tags = tags.union(set(self.new_refs["tags"]))
except Exception:
pass
- tags = [
- {
- 'name': tag,
- 'url': tag_url(tag)
- }
- for tag in tags
- ]
+ tags = [{"name": tag, "url": tag_url(tag)} for tag in tags]
- data['push'] = {
- 'commits': commits,
- 'issues': issues,
- 'branches': branches,
- 'tags': tags,
+ data["push"] = {
+ "commits": commits,
+ "issues": issues,
+ "branches": branches,
+ "tags": tags,
}
return data
diff --git a/rhodecode/events/repo_group.py b/rhodecode/events/repo_group.py
--- a/rhodecode/events/repo_group.py
+++ b/rhodecode/events/repo_group.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -29,27 +29,31 @@ class RepoGroupEvent(RhodeCodeIntegratio
"""
Base class for events acting on a repository group.
- :param repo: a :class:`RepositoryGroup` instance
+ :param repo_group: a :class:`RepositoryGroup` instance
"""
- def __init__(self, repo_group):
- super().__init__()
+ def __init__(self, repo_group, context=None):
+ super().__init__(context=context)
self.repo_group = repo_group
+ self.context = self._context
def as_dict(self):
data = super().as_dict()
- data.update({
- 'repo_group': {
- 'group_id': self.repo_group.group_id,
- 'group_name': self.repo_group.group_name,
- 'group_parent_id': self.repo_group.group_parent_id,
- 'group_description': self.repo_group.group_description,
- 'user_id': self.repo_group.user_id,
- 'created_by': self.repo_group.user.username,
- 'created_on': self.repo_group.created_on,
- 'enable_locking': self.repo_group.enable_locking,
+ data.update(
+ {
+ "repo_group": {
+ "group_id": self.repo_group.group_id,
+ "group_name": self.repo_group.group_name,
+ "group_parent_id": self.repo_group.group_parent_id,
+ "group_description": self.repo_group.group_description,
+ "user_id": self.repo_group.user_id,
+ "created_by": self.repo_group.user.username,
+ "created_on": self.repo_group.created_on,
+ "enable_locking": self.repo_group.enable_locking,
+ },
+ "context": self.context
}
- })
+ )
return data
@@ -58,9 +62,10 @@ class RepoGroupCreateEvent(RepoGroupEven
An instance of this class is emitted as an :term:`event` whenever a
repository group is created.
"""
- name = 'repo-group-create'
- display_name = lazy_ugettext('repository group created')
- description = lazy_ugettext('Event triggered after a repository group was created')
+
+ name = "repo-group-create"
+ display_name = lazy_ugettext("repository group created")
+ description = lazy_ugettext("Event triggered after a repository group was created")
class RepoGroupDeleteEvent(RepoGroupEvent):
@@ -68,9 +73,10 @@ class RepoGroupDeleteEvent(RepoGroupEven
An instance of this class is emitted as an :term:`event` whenever a
repository group is deleted.
"""
- name = 'repo-group-delete'
- display_name = lazy_ugettext('repository group deleted')
- description = lazy_ugettext('Event triggered after a repository group was deleted')
+
+ name = "repo-group-delete"
+ display_name = lazy_ugettext("repository group deleted")
+ description = lazy_ugettext("Event triggered after a repository group was deleted")
class RepoGroupUpdateEvent(RepoGroupEvent):
@@ -78,6 +84,7 @@ class RepoGroupUpdateEvent(RepoGroupEven
An instance of this class is emitted as an :term:`event` whenever a
repository group is updated.
"""
- name = 'repo-group-update'
- display_name = lazy_ugettext('repository group update')
- description = lazy_ugettext('Event triggered after a repository group was updated')
+
+ name = "repo-group-update"
+ display_name = lazy_ugettext("repository group update")
+ description = lazy_ugettext("Event triggered after a repository group was updated")
diff --git a/rhodecode/events/user.py b/rhodecode/events/user.py
--- a/rhodecode/events/user.py
+++ b/rhodecode/events/user.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,8 +21,7 @@ from zope.interface import implementer
from rhodecode.translation import lazy_ugettext
from rhodecode.events.base import RhodecodeEvent, RhodeCodeIntegrationEvent
-from rhodecode.events.interfaces import (
- IUserRegistered, IUserPreCreate, IUserPreUpdate)
+from rhodecode.events.interfaces import IUserRegistered, IUserPreCreate, IUserPreUpdate
log = logging.getLogger(__name__)
@@ -33,8 +32,9 @@ class UserRegistered(RhodeCodeIntegratio
An instance of this class is emitted as an :term:`event` whenever a user
account is registered.
"""
- name = 'user-register'
- display_name = lazy_ugettext('user registered')
+
+ name = "user-register"
+ display_name = lazy_ugettext("user registered")
def __init__(self, user, session):
super().__init__()
@@ -48,8 +48,9 @@ class UserPreCreate(RhodeCodeIntegration
An instance of this class is emitted as an :term:`event` before a new user
object is created.
"""
- name = 'user-pre-create'
- display_name = lazy_ugettext('user pre create')
+
+ name = "user-pre-create"
+ display_name = lazy_ugettext("user pre create")
def __init__(self, user_data):
super().__init__()
@@ -62,8 +63,9 @@ class UserPostCreate(RhodeCodeIntegratio
An instance of this class is emitted as an :term:`event` after a new user
object is created.
"""
- name = 'user-post-create'
- display_name = lazy_ugettext('user post create')
+
+ name = "user-post-create"
+ display_name = lazy_ugettext("user post create")
def __init__(self, user_data):
super().__init__()
@@ -76,8 +78,9 @@ class UserPreUpdate(RhodeCodeIntegration
An instance of this class is emitted as an :term:`event` before a user
object is updated.
"""
- name = 'user-pre-update'
- display_name = lazy_ugettext('user pre update')
+
+ name = "user-pre-update"
+ display_name = lazy_ugettext("user pre update")
def __init__(self, user, user_data):
super().__init__()
@@ -87,8 +90,8 @@ class UserPreUpdate(RhodeCodeIntegration
class UserPermissionsChange(RhodecodeEvent):
"""
- This event should be triggered on an event that permissions of user might changed.
- Currently this should be triggered on:
+ This event should be triggered on an event that permissions of user might be changed.
+ Currently, this should be triggered on:
- user added/removed from user group
- repo permissions changed
@@ -96,9 +99,11 @@ class UserPermissionsChange(RhodecodeEve
- user group permissions changed
"""
- name = 'user-permissions-change'
- display_name = lazy_ugettext('user permissions change')
+
+ name = "user-permissions-change"
+ display_name = lazy_ugettext("user permissions change")
- def __init__(self, user_ids):
- super().__init__()
+ def __init__(self, user_ids, context=None):
+ super().__init__(context=context)
self.user_ids = user_ids
+ self.context = self._context
diff --git a/rhodecode/forms/__init__.py b/rhodecode/forms/__init__.py
--- a/rhodecode/forms/__init__.py
+++ b/rhodecode/forms/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/__init__.py b/rhodecode/integrations/__init__.py
--- a/rhodecode/integrations/__init__.py
+++ b/rhodecode/integrations/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/registry.py b/rhodecode/integrations/registry.py
--- a/rhodecode/integrations/registry.py
+++ b/rhodecode/integrations/registry.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/routes.py b/rhodecode/integrations/routes.py
--- a/rhodecode/integrations/routes.py
+++ b/rhodecode/integrations/routes.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/schema.py b/rhodecode/integrations/schema.py
--- a/rhodecode/integrations/schema.py
+++ b/rhodecode/integrations/schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/tests/__init__.py b/rhodecode/integrations/tests/__init__.py
--- a/rhodecode/integrations/tests/__init__.py
+++ b/rhodecode/integrations/tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/tests/test_integrations.py b/rhodecode/integrations/tests/test_integrations.py
--- a/rhodecode/integrations/tests/test_integrations.py
+++ b/rhodecode/integrations/tests/test_integrations.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/__init__.py b/rhodecode/integrations/types/__init__.py
--- a/rhodecode/integrations/types/__init__.py
+++ b/rhodecode/integrations/types/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/base.py b/rhodecode/integrations/types/base.py
--- a/rhodecode/integrations/types/base.py
+++ b/rhodecode/integrations/types/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/email.py b/rhodecode/integrations/types/email.py
--- a/rhodecode/integrations/types/email.py
+++ b/rhodecode/integrations/types/email.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/handlers/__init__.py b/rhodecode/integrations/types/handlers/__init__.py
--- a/rhodecode/integrations/types/handlers/__init__.py
+++ b/rhodecode/integrations/types/handlers/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/handlers/slack.py b/rhodecode/integrations/types/handlers/slack.py
--- a/rhodecode/integrations/types/handlers/slack.py
+++ b/rhodecode/integrations/types/handlers/slack.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/handlers/webhook.py b/rhodecode/integrations/types/handlers/webhook.py
--- a/rhodecode/integrations/types/handlers/webhook.py
+++ b/rhodecode/integrations/types/handlers/webhook.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/slack.py b/rhodecode/integrations/types/slack.py
--- a/rhodecode/integrations/types/slack.py
+++ b/rhodecode/integrations/types/slack.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/types/webhook.py b/rhodecode/integrations/types/webhook.py
--- a/rhodecode/integrations/types/webhook.py
+++ b/rhodecode/integrations/types/webhook.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/integrations/views.py b/rhodecode/integrations/views.py
--- a/rhodecode/integrations/views.py
+++ b/rhodecode/integrations/views.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/__init__.py b/rhodecode/lib/__init__.py
--- a/rhodecode/lib/__init__.py
+++ b/rhodecode/lib/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/_vendor/__init__.py b/rhodecode/lib/_vendor/__init__.py
--- a/rhodecode/lib/_vendor/__init__.py
+++ b/rhodecode/lib/_vendor/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/_vendor/authomatic/six.py b/rhodecode/lib/_vendor/authomatic/six.py
--- a/rhodecode/lib/_vendor/authomatic/six.py
+++ b/rhodecode/lib/_vendor/authomatic/six.py
@@ -1,7 +1,5 @@
-"""Utilities for writing code that runs on Python 2 and 3"""
-
-# Copyright (c) 2010-2015 Benjamin Peterson
+"""Utilities for writing code that runs on Python 2 and 3"""# Copyright (C) 2010-2015 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/rhodecode/lib/action_parser.py b/rhodecode/lib/action_parser.py
--- a/rhodecode/lib/action_parser.py
+++ b/rhodecode/lib/action_parser.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/api_utils.py b/rhodecode/lib/api_utils.py
--- a/rhodecode/lib/api_utils.py
+++ b/rhodecode/lib/api_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/audit_logger.py b/rhodecode/lib/audit_logger.py
--- a/rhodecode/lib/audit_logger.py
+++ b/rhodecode/lib/audit_logger.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py
--- a/rhodecode/lib/auth.py
+++ b/rhodecode/lib/auth.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py
--- a/rhodecode/lib/base.py
+++ b/rhodecode/lib/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/caching_query.py b/rhodecode/lib/caching_query.py
--- a/rhodecode/lib/caching_query.py
+++ b/rhodecode/lib/caching_query.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/celerylib/__init__.py b/rhodecode/lib/celerylib/__init__.py
--- a/rhodecode/lib/celerylib/__init__.py
+++ b/rhodecode/lib/celerylib/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/celerylib/loader.py b/rhodecode/lib/celerylib/loader.py
--- a/rhodecode/lib/celerylib/loader.py
+++ b/rhodecode/lib/celerylib/loader.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/celerylib/scheduler.py b/rhodecode/lib/celerylib/scheduler.py
--- a/rhodecode/lib/celerylib/scheduler.py
+++ b/rhodecode/lib/celerylib/scheduler.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/celerylib/utils.py b/rhodecode/lib/celerylib/utils.py
--- a/rhodecode/lib/celerylib/utils.py
+++ b/rhodecode/lib/celerylib/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/channelstream.py b/rhodecode/lib/channelstream.py
--- a/rhodecode/lib/channelstream.py
+++ b/rhodecode/lib/channelstream.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/codeblocks.py b/rhodecode/lib/codeblocks.py
--- a/rhodecode/lib/codeblocks.py
+++ b/rhodecode/lib/codeblocks.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -478,7 +478,9 @@ class DiffSet(object):
log.debug('rendering diff for %r', patch['filename'])
source_filename = patch['original_filename']
+ source_filename_bytes = patch['original_filename_bytes']
target_filename = patch['filename']
+ target_filename_bytes = patch['filename_bytes']
source_lexer = plain_text_lexer
target_lexer = plain_text_lexer
@@ -491,12 +493,12 @@ class DiffSet(object):
if (source_filename and patch['operation'] in ('D', 'M')
and source_filename not in self.source_nodes):
self.source_nodes[source_filename] = (
- self.source_node_getter(source_filename))
+ self.source_node_getter(source_filename_bytes))
if (target_filename and patch['operation'] in ('A', 'M')
and target_filename not in self.target_nodes):
self.target_nodes[target_filename] = (
- self.target_node_getter(target_filename))
+ self.target_node_getter(target_filename_bytes))
elif hl_mode == self.HL_FAST:
source_lexer = self._get_lexer_for_filename(source_filename)
@@ -558,6 +560,7 @@ class DiffSet(object):
})
file_chunks = patch['chunks'][1:]
+
for i, hunk in enumerate(file_chunks, 1):
hunkbit = self.parse_hunk(hunk, source_file, target_file)
hunkbit.source_file_path = source_file_path
@@ -593,12 +596,13 @@ class DiffSet(object):
return filediff
def parse_hunk(self, hunk, source_file, target_file):
+
result = AttributeDict(dict(
source_start=hunk['source_start'],
source_length=hunk['source_length'],
target_start=hunk['target_start'],
target_length=hunk['target_length'],
- section_header=hunk['section_header'],
+ section_header=safe_str(hunk['section_header']),
lines=[],
))
before, after = [], []
diff --git a/rhodecode/lib/colander_utils.py b/rhodecode/lib/colander_utils.py
--- a/rhodecode/lib/colander_utils.py
+++ b/rhodecode/lib/colander_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/colored_formatter.py b/rhodecode/lib/colored_formatter.py
--- a/rhodecode/lib/colored_formatter.py
+++ b/rhodecode/lib/colored_formatter.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/config_utils.py b/rhodecode/lib/config_utils.py
--- a/rhodecode/lib/config_utils.py
+++ b/rhodecode/lib/config_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/datelib.py b/rhodecode/lib/datelib.py
--- a/rhodecode/lib/datelib.py
+++ b/rhodecode/lib/datelib.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/db_manage.py b/rhodecode/lib/db_manage.py
--- a/rhodecode/lib/db_manage.py
+++ b/rhodecode/lib/db_manage.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -307,7 +307,7 @@ class DbManage(object):
def create_test_admin_and_users(self):
log.info('creating admin and regular test users')
- from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
+ from rhodecode.bootstrap import TEST_USER_ADMIN_LOGIN, \
TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
@@ -328,8 +328,6 @@ class DbManage(object):
and disables dotencode
"""
settings_model = SettingsModel(sa=self.sa)
- from rhodecode.lib.vcs.backends.hg import largefiles_store
- from rhodecode.lib.vcs.backends.git import lfs_store
# Build HOOKS
hooks = [
@@ -360,24 +358,6 @@ class DbManage(object):
largefiles.ui_value = ''
self.sa.add(largefiles)
- # set default largefiles cache dir, defaults to
- # /repo_store_location/.cache/largefiles
- largefiles = RhodeCodeUi()
- largefiles.ui_section = 'largefiles'
- largefiles.ui_key = 'usercache'
- largefiles.ui_value = largefiles_store(repo_store_path)
-
- self.sa.add(largefiles)
-
- # set default lfs cache dir, defaults to
- # /repo_store_location/.cache/lfs_store
- lfsstore = RhodeCodeUi()
- lfsstore.ui_section = 'vcs_git_lfs'
- lfsstore.ui_key = 'store_location'
- lfsstore.ui_value = lfs_store(repo_store_path)
-
- self.sa.add(lfsstore)
-
# enable hgevolve disabled by default
hgevolve = RhodeCodeUi()
hgevolve.ui_section = 'extensions'
diff --git a/rhodecode/lib/dbmigrate/__init__.py b/rhodecode/lib/dbmigrate/__init__.py
--- a/rhodecode/lib/dbmigrate/__init__.py
+++ b/rhodecode/lib/dbmigrate/__init__.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/__init__.py b/rhodecode/lib/dbmigrate/schema/__init__.py
--- a/rhodecode/lib/dbmigrate/schema/__init__.py
+++ b/rhodecode/lib/dbmigrate/schema/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_1_0.py b/rhodecode/lib/dbmigrate/schema/db_1_1_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_1_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_1_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_4_0.py b/rhodecode/lib/dbmigrate/schema/db_1_4_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_4_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_4_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_5_0.py b/rhodecode/lib/dbmigrate/schema/db_1_5_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_5_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_5_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_5_2.py b/rhodecode/lib/dbmigrate/schema/db_1_5_2.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_5_2.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_5_2.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_6_0.py b/rhodecode/lib/dbmigrate/schema/db_1_6_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_6_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_6_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_7_0.py b/rhodecode/lib/dbmigrate/schema/db_1_7_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_7_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_7_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_8_0.py b/rhodecode/lib/dbmigrate/schema/db_1_8_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_8_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_8_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_0_0.py b/rhodecode/lib/dbmigrate/schema/db_2_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_0_1.py b/rhodecode/lib/dbmigrate/schema/db_2_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_0_2.py b/rhodecode/lib/dbmigrate/schema/db_2_0_2.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_0_2.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_0_2.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_1_0.py b/rhodecode/lib/dbmigrate/schema/db_2_1_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_1_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_1_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_2_0.py b/rhodecode/lib/dbmigrate/schema/db_2_2_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_2_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_2_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_2_3.py b/rhodecode/lib/dbmigrate/schema/db_2_2_3.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_2_3.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_2_3.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py b/rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py b/rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py b/rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py
--- a/rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py
+++ b/rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py b/rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py b/rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py b/rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py b/rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py b/rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py b/rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py b/rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py b/rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py b/rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py b/rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py b/rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py b/rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py b/rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py b/rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py b/rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/utils.py b/rhodecode/lib/dbmigrate/utils.py
--- a/rhodecode/lib/dbmigrate/utils.py
+++ b/rhodecode/lib/dbmigrate/utils.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/dbmigrate/versions/__init__.py b/rhodecode/lib/dbmigrate/versions/__init__.py
--- a/rhodecode/lib/dbmigrate/versions/__init__.py
+++ b/rhodecode/lib/dbmigrate/versions/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/diffs.py b/rhodecode/lib/diffs.py
--- a/rhodecode/lib/diffs.py
+++ b/rhodecode/lib/diffs.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -455,7 +455,9 @@ class DiffProcessor(object):
return arg
for chunk in self._diff.chunks():
+ bytes_head = chunk.header
head = chunk.header_as_str
+
log.debug('parsing diff chunk %r', chunk)
raw_diff = chunk.raw
@@ -598,10 +600,17 @@ class DiffProcessor(object):
chunks.insert(0, frag)
- original_filename = safe_str(head['a_path'])
+ original_filename = head['a_path']
+ original_filename_bytes = bytes_head['a_path']
+
+ filename = head['b_path']
+ filename_bytes = bytes_head['b_path']
+
_files.append({
'original_filename': original_filename,
- 'filename': safe_str(head['b_path']),
+ 'original_filename_bytes': original_filename_bytes,
+ 'filename': filename,
+ 'filename_bytes': filename_bytes,
'old_revision': head['a_blob_id'],
'new_revision': head['b_blob_id'],
'chunks': chunks,
diff --git a/rhodecode/lib/enc_utils.py b/rhodecode/lib/enc_utils.py
--- a/rhodecode/lib/enc_utils.py
+++ b/rhodecode/lib/enc_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/encrypt.py b/rhodecode/lib/encrypt.py
--- a/rhodecode/lib/encrypt.py
+++ b/rhodecode/lib/encrypt.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/exc_tracking.py b/rhodecode/lib/exc_tracking.py
--- a/rhodecode/lib/exc_tracking.py
+++ b/rhodecode/lib/exc_tracking.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/exceptions.py b/rhodecode/lib/exceptions.py
--- a/rhodecode/lib/exceptions.py
+++ b/rhodecode/lib/exceptions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,8 +20,7 @@
Set of custom exceptions used in RhodeCode
"""
-from webob.exc import HTTPClientError
-from pyramid.httpexceptions import HTTPBadGateway
+from pyramid.httpexceptions import HTTPBadGateway, HTTPClientError
class LdapUsernameError(Exception):
@@ -102,12 +101,7 @@ class HTTPRequirementError(HTTPClientErr
self.args = (message, )
-class ClientNotSupportedError(HTTPRequirementError):
- title = explanation = 'Client Not Supported'
- reason = None
-
-
-class HTTPLockedRC(HTTPClientError):
+class HTTPLockedRepo(HTTPClientError):
"""
Special Exception For locked Repos in RhodeCode, the return code can
be overwritten by _code keyword argument passed into constructors
@@ -131,14 +125,13 @@ class HTTPBranchProtected(HTTPClientErro
Special Exception For Indicating that branch is protected in RhodeCode, the
return code can be overwritten by _code keyword argument passed into constructors
"""
- code = 403
title = explanation = 'Branch Protected'
reason = None
- def __init__(self, message, *args, **kwargs):
- self.title = self.explanation = message
- super().__init__(*args, **kwargs)
- self.args = (message, )
+
+class ClientNotSupported(HTTPRequirementError):
+ title = explanation = 'Client Not Supported'
+ reason = None
class IMCCommitError(Exception):
diff --git a/rhodecode/lib/ext_json_renderer.py b/rhodecode/lib/ext_json_renderer.py
--- a/rhodecode/lib/ext_json_renderer.py
+++ b/rhodecode/lib/ext_json_renderer.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/feedgenerator/__init__.py b/rhodecode/lib/feedgenerator/__init__.py
--- a/rhodecode/lib/feedgenerator/__init__.py
+++ b/rhodecode/lib/feedgenerator/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/graphmod.py b/rhodecode/lib/graphmod.py
--- a/rhodecode/lib/graphmod.py
+++ b/rhodecode/lib/graphmod.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/hash_utils.py b/rhodecode/lib/hash_utils.py
--- a/rhodecode/lib/hash_utils.py
+++ b/rhodecode/lib/hash_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -51,7 +51,6 @@ from pygments.formatters.html import Htm
from pygments.lexers import (
get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
-from pyramid.threadlocal import get_current_request
from tempita import looper
from webhelpers2.html import literal, HTML, escape
from webhelpers2.html._autolink import _auto_link_urls
@@ -89,6 +88,7 @@ from rhodecode.lib.utils2 import (
str2bool,
get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
AttributeDict, safe_int, md5, md5_safe, get_host_info)
+from rhodecode.lib.pyramid_utils import get_current_request
from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
diff --git a/rhodecode/lib/hook_daemon/__init__.py b/rhodecode/lib/hook_daemon/__init__.py
--- a/rhodecode/lib/hook_daemon/__init__.py
+++ b/rhodecode/lib/hook_daemon/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/hook_daemon/base.py b/rhodecode/lib/hook_daemon/base.py
--- a/rhodecode/lib/hook_daemon/base.py
+++ b/rhodecode/lib/hook_daemon/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -16,13 +16,14 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
-import os
-import time
import logging
+import traceback
-from rhodecode.lib.config_utils import get_config
+from rhodecode.model import meta
+from rhodecode.lib import hooks_base
+from rhodecode.lib.utils2 import AttributeDict
+from rhodecode.lib.exceptions import HTTPLockedRepo, HTTPBranchProtected
-from rhodecode.lib.svn_txn_utils import get_txn_id_from_store
log = logging.getLogger(__name__)
@@ -42,48 +43,82 @@ class BaseHooksCallbackDaemon:
log.debug('Exiting `%s` callback daemon', self.__class__.__name__)
-class HooksModuleCallbackDaemon(BaseHooksCallbackDaemon):
+class Hooks(object):
+ """
+ Exposes the hooks module for calling them using the local HooksModuleCallbackDaemon
+ """
+ def __init__(self, request=None, log_prefix=''):
+ self.log_prefix = log_prefix
+ self.request = request
- def __init__(self, module):
- super().__init__()
- self.hooks_module = module
+ def repo_size(self, extras):
+ log.debug("%sCalled repo_size of %s object", self.log_prefix, self)
+ return self._call_hook(hooks_base.repo_size, extras)
- def __repr__(self):
- return f'HooksModuleCallbackDaemon(hooks_module={self.hooks_module})'
-
+ def pre_pull(self, extras):
+ log.debug("%sCalled pre_pull of %s object", self.log_prefix, self)
+ return self._call_hook(hooks_base.pre_pull, extras)
-def prepare_callback_daemon(extras, protocol, host, txn_id=None):
+ def post_pull(self, extras):
+ log.debug("%sCalled post_pull of %s object", self.log_prefix, self)
+ return self._call_hook(hooks_base.post_pull, extras)
+
+ def pre_push(self, extras):
+ log.debug("%sCalled pre_push of %s object", self.log_prefix, self)
+ return self._call_hook(hooks_base.pre_push, extras)
- match protocol:
- case 'http':
- from rhodecode.lib.hook_daemon.http_hooks_deamon import HttpHooksCallbackDaemon
- port = 0
- if txn_id:
- # read txn-id to re-use the PORT for callback daemon
- repo_path = os.path.join(extras['repo_store'], extras['repository'])
- txn_details = get_txn_id_from_store(repo_path, txn_id)
- port = txn_details.get('port', 0)
+ def post_push(self, extras):
+ log.debug("%sCalled post_push of %s object", self.log_prefix, self)
+ return self._call_hook(hooks_base.post_push, extras)
+
+ def _call_hook(self, hook, extras):
+ extras = AttributeDict(extras)
+ _server_url = extras['server_url']
- callback_daemon = HttpHooksCallbackDaemon(
- txn_id=txn_id, host=host, port=port)
- case 'celery':
- from rhodecode.lib.hook_daemon.celery_hooks_deamon import CeleryHooksCallbackDaemon
- callback_daemon = CeleryHooksCallbackDaemon(get_config(extras['config']))
- case 'local':
- from rhodecode.lib.hook_daemon.hook_module import Hooks
- callback_daemon = HooksModuleCallbackDaemon(Hooks.__module__)
- case _:
- log.error('Unsupported callback daemon protocol "%s"', protocol)
- raise Exception('Unsupported callback daemon protocol.')
+ extras.request = self.request
+ try:
+ result = hook(extras)
+ if result is None:
+ raise Exception(f'Failed to obtain hook result from func: {hook}')
+ except HTTPBranchProtected as error:
+ # Those special cases don't need error reporting. It's a case of
+ # locked repo or protected branch
+ result = AttributeDict({
+ 'status': error.code,
+ 'output': error.explanation
+ })
+ except HTTPLockedRepo as error:
+ # Those special cases don't need error reporting. It's a case of
+ # locked repo or protected branch
+ result = AttributeDict({
+ 'status': error.code,
+ 'output': error.explanation
+ })
+ except Exception as error:
+ # locked needs different handling since we need to also
+ # handle PULL operations
+ log.exception('%sException when handling hook %s', self.log_prefix, hook)
+ exc_tb = traceback.format_exc()
+ error_args = error.args
+ return {
+ 'status': 128,
+ 'output': '',
+ 'exception': type(error).__name__,
+ 'exception_traceback': exc_tb,
+ 'exception_args': error_args,
+ }
+ finally:
+ meta.Session.remove()
- extras['hooks_uri'] = getattr(callback_daemon, 'hooks_uri', '')
- extras['task_queue'] = getattr(callback_daemon, 'task_queue', '')
- extras['task_backend'] = getattr(callback_daemon, 'task_backend', '')
- extras['hooks_protocol'] = protocol
- extras['time'] = time.time()
+ log.debug('%sGot hook call response %s', self.log_prefix, result)
+ return {
+ 'status': result.status,
+ 'output': result.output,
+ }
- # register txn_id
- extras['txn_id'] = txn_id
- log.debug('Prepared a callback daemon: %s',
- callback_daemon.__class__.__name__)
- return callback_daemon, extras
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ pass
+
diff --git a/rhodecode/lib/hook_daemon/celery_hooks_deamon.py b/rhodecode/lib/hook_daemon/celery_hooks_deamon.py
--- a/rhodecode/lib/hook_daemon/celery_hooks_deamon.py
+++ b/rhodecode/lib/hook_daemon/celery_hooks_deamon.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,12 +22,16 @@ from rhodecode.lib.hook_daemon.base impo
class CeleryHooksCallbackDaemon(BaseHooksCallbackDaemon):
"""
Context manger for achieving a compatibility with celery backend
+ It is calling a call to vcsserver, where it uses HooksCeleryClient to actually call a task from
+
+ f'rhodecode.lib.celerylib.tasks.{method}'
+
"""
- def __init__(self, config):
- # TODO: replace this with settings bootstrapped...
- self.task_queue = config.get('app:main', 'celery.broker_url')
- self.task_backend = config.get('app:main', 'celery.result_backend')
+ def __init__(self, broker_url, result_backend):
+ super().__init__()
+ self.broker_url = broker_url
+ self.result_backend = result_backend
def __repr__(self):
- return f'CeleryHooksCallbackDaemon(task_queue={self.task_queue}, task_backend={self.task_backend})'
+ return f'CeleryHooksCallbackDaemon(broker_url={self.broker_url}, result_backend={self.result_backend})'
diff --git a/rhodecode/lib/hook_daemon/hook_module.py b/rhodecode/lib/hook_daemon/hook_module.py
--- a/rhodecode/lib/hook_daemon/hook_module.py
+++ b/rhodecode/lib/hook_daemon/hook_module.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -17,88 +17,18 @@
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
-import traceback
-from rhodecode.model import meta
-
-from rhodecode.lib import hooks_base
-from rhodecode.lib.exceptions import HTTPLockedRC, HTTPBranchProtected
-from rhodecode.lib.utils2 import AttributeDict
+from rhodecode.lib.hook_daemon.base import BaseHooksCallbackDaemon
log = logging.getLogger(__name__)
-class Hooks(object):
- """
- Exposes the hooks for remote callbacks
- """
- def __init__(self, request=None, log_prefix=''):
- self.log_prefix = log_prefix
- self.request = request
-
- def repo_size(self, extras):
- log.debug("%sCalled repo_size of %s object", self.log_prefix, self)
- return self._call_hook(hooks_base.repo_size, extras)
-
- def pre_pull(self, extras):
- log.debug("%sCalled pre_pull of %s object", self.log_prefix, self)
- return self._call_hook(hooks_base.pre_pull, extras)
-
- def post_pull(self, extras):
- log.debug("%sCalled post_pull of %s object", self.log_prefix, self)
- return self._call_hook(hooks_base.post_pull, extras)
-
- def pre_push(self, extras):
- log.debug("%sCalled pre_push of %s object", self.log_prefix, self)
- return self._call_hook(hooks_base.pre_push, extras)
-
- def post_push(self, extras):
- log.debug("%sCalled post_push of %s object", self.log_prefix, self)
- return self._call_hook(hooks_base.post_push, extras)
-
- def _call_hook(self, hook, extras):
- extras = AttributeDict(extras)
- _server_url = extras['server_url']
-
- extras.request = self.request
+class HooksModuleCallbackDaemon(BaseHooksCallbackDaemon):
- try:
- result = hook(extras)
- if result is None:
- raise Exception(f'Failed to obtain hook result from func: {hook}')
- except HTTPBranchProtected as error:
- # Those special cases don't need error reporting. It's a case of
- # locked repo or protected branch
- result = AttributeDict({
- 'status': error.code,
- 'output': error.explanation
- })
- except (HTTPLockedRC, Exception) as error:
- # locked needs different handling since we need to also
- # handle PULL operations
- exc_tb = ''
- if not isinstance(error, HTTPLockedRC):
- exc_tb = traceback.format_exc()
- log.exception('%sException when handling hook %s', self.log_prefix, hook)
- error_args = error.args
- return {
- 'status': 128,
- 'output': '',
- 'exception': type(error).__name__,
- 'exception_traceback': exc_tb,
- 'exception_args': error_args,
- }
- finally:
- meta.Session.remove()
+ def __init__(self, module):
+ super().__init__()
+ self.hooks_module = module
- log.debug('%sGot hook call response %s', self.log_prefix, result)
- return {
- 'status': result.status,
- 'output': result.output,
- }
+ def __repr__(self):
+ return f'HooksModuleCallbackDaemon(hooks_module={self.hooks_module})'
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- pass
diff --git a/rhodecode/lib/hook_daemon/http_hooks_deamon.py b/rhodecode/lib/hook_daemon/http_hooks_deamon.py
deleted file mode 100644
--- a/rhodecode/lib/hook_daemon/http_hooks_deamon.py
+++ /dev/null
@@ -1,287 +0,0 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# 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 Affero General Public License
-# along with this program. If not, see .
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-import os
-import logging
-import traceback
-import threading
-import socket
-import msgpack
-import gevent
-
-from http.server import BaseHTTPRequestHandler
-from socketserver import TCPServer
-
-from rhodecode.model import meta
-from rhodecode.lib.ext_json import json
-from rhodecode.lib import rc_cache
-from rhodecode.lib.svn_txn_utils import get_txn_id_data_key
-from rhodecode.lib.hook_daemon.hook_module import Hooks
-
-log = logging.getLogger(__name__)
-
-
-class HooksHttpHandler(BaseHTTPRequestHandler):
-
- JSON_HOOKS_PROTO = 'json.v1'
- MSGPACK_HOOKS_PROTO = 'msgpack.v1'
- # starting with RhodeCode 5.0.0 MsgPack is the default, prior it used json
- DEFAULT_HOOKS_PROTO = MSGPACK_HOOKS_PROTO
-
- @classmethod
- def serialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO):
- if proto == cls.MSGPACK_HOOKS_PROTO:
- return msgpack.packb(data)
- return json.dumps(data)
-
- @classmethod
- def deserialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO):
- if proto == cls.MSGPACK_HOOKS_PROTO:
- return msgpack.unpackb(data)
- return json.loads(data)
-
- def do_POST(self):
- hooks_proto, method, extras = self._read_request()
- log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto)
-
- txn_id = getattr(self.server, 'txn_id', None)
- if txn_id:
- log.debug('Computing TXN_ID based on `%s`:`%s`',
- extras['repository'], extras['txn_id'])
- computed_txn_id = rc_cache.utils.compute_key_from_params(
- extras['repository'], extras['txn_id'])
- if txn_id != computed_txn_id:
- raise Exception(
- 'TXN ID fail: expected {} got {} instead'.format(
- txn_id, computed_txn_id))
-
- request = getattr(self.server, 'request', None)
- try:
- hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address))
- result = self._call_hook_method(hooks, method, extras)
-
- except Exception as e:
- exc_tb = traceback.format_exc()
- result = {
- 'exception': e.__class__.__name__,
- 'exception_traceback': exc_tb,
- 'exception_args': e.args
- }
- self._write_response(hooks_proto, result)
-
- def _read_request(self):
- length = int(self.headers['Content-Length'])
- # respect sent headers, fallback to OLD proto for compatability
- hooks_proto = self.headers.get('rc-hooks-protocol') or self.JSON_HOOKS_PROTO
- if hooks_proto == self.MSGPACK_HOOKS_PROTO:
- # support for new vcsserver msgpack based protocol hooks
- body = self.rfile.read(length)
- data = self.deserialize_data(body)
- else:
- body = self.rfile.read(length)
- data = self.deserialize_data(body)
-
- return hooks_proto, data['method'], data['extras']
-
- def _write_response(self, hooks_proto, result):
- self.send_response(200)
- if hooks_proto == self.MSGPACK_HOOKS_PROTO:
- self.send_header("Content-type", "application/msgpack")
- self.end_headers()
- data = self.serialize_data(result)
- self.wfile.write(data)
- else:
- self.send_header("Content-type", "text/json")
- self.end_headers()
- data = self.serialize_data(result)
- self.wfile.write(data)
-
- def _call_hook_method(self, hooks, method, extras):
- try:
- result = getattr(hooks, method)(extras)
- finally:
- meta.Session.remove()
- return result
-
- def log_message(self, format, *args):
- """
- This is an overridden method of BaseHTTPRequestHandler which logs using
- a logging library instead of writing directly to stderr.
- """
-
- message = format % args
-
- log.debug(
- "HOOKS: client=%s - - [%s] %s", self.client_address,
- self.log_date_time_string(), message)
-
-
-class ThreadedHookCallbackDaemon(object):
-
- _callback_thread = None
- _daemon = None
- _done = False
- use_gevent = False
-
- def __init__(self, txn_id=None, host=None, port=None):
- self._prepare(txn_id=txn_id, host=host, port=port)
- if self.use_gevent:
- self._run_func = self._run_gevent
- self._stop_func = self._stop_gevent
- else:
- self._run_func = self._run
- self._stop_func = self._stop
-
- def __enter__(self):
- log.debug('Running `%s` callback daemon', self.__class__.__name__)
- self._run_func()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- log.debug('Exiting `%s` callback daemon', self.__class__.__name__)
- self._stop_func()
-
- def _prepare(self, txn_id=None, host=None, port=None):
- raise NotImplementedError()
-
- def _run(self):
- raise NotImplementedError()
-
- def _stop(self):
- raise NotImplementedError()
-
- def _run_gevent(self):
- raise NotImplementedError()
-
- def _stop_gevent(self):
- raise NotImplementedError()
-
-
-class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
- """
- Context manager which will run a callback daemon in a background thread.
- """
-
- hooks_uri = None
-
- # From Python docs: Polling reduces our responsiveness to a shutdown
- # request and wastes cpu at all other times.
- POLL_INTERVAL = 0.01
-
- use_gevent = False
-
- def __repr__(self):
- return f'HttpHooksCallbackDaemon(hooks_uri={self.hooks_uri})'
-
- @property
- def _hook_prefix(self):
- return f'HOOKS: {self.hooks_uri} '
-
- def get_hostname(self):
- return socket.gethostname() or '127.0.0.1'
-
- def get_available_port(self, min_port=20000, max_port=65535):
- from rhodecode.lib.utils2 import get_available_port as _get_port
- return _get_port(min_port, max_port)
-
- def _prepare(self, txn_id=None, host=None, port=None):
- from pyramid.threadlocal import get_current_request
-
- if not host or host == "*":
- host = self.get_hostname()
- if not port:
- port = self.get_available_port()
-
- server_address = (host, port)
- self.hooks_uri = f'{host}:{port}'
- self.txn_id = txn_id
- self._done = False
-
- log.debug(
- "%s Preparing HTTP callback daemon registering hook object: %s",
- self._hook_prefix, HooksHttpHandler)
-
- self._daemon = TCPServer(server_address, HooksHttpHandler)
- # inject transaction_id for later verification
- self._daemon.txn_id = self.txn_id
-
- # pass the WEB app request into daemon
- self._daemon.request = get_current_request()
-
- def _run(self):
- log.debug("Running thread-based loop of callback daemon in background")
- callback_thread = threading.Thread(
- target=self._daemon.serve_forever,
- kwargs={'poll_interval': self.POLL_INTERVAL})
- callback_thread.daemon = True
- callback_thread.start()
- self._callback_thread = callback_thread
-
- def _run_gevent(self):
- log.debug("Running gevent-based loop of callback daemon in background")
- # create a new greenlet for the daemon's serve_forever method
- callback_greenlet = gevent.spawn(
- self._daemon.serve_forever,
- poll_interval=self.POLL_INTERVAL)
-
- # store reference to greenlet
- self._callback_greenlet = callback_greenlet
-
- # switch to this greenlet
- gevent.sleep(0.01)
-
- def _stop(self):
- log.debug("Waiting for background thread to finish.")
- self._daemon.shutdown()
- self._callback_thread.join()
- self._daemon = None
- self._callback_thread = None
- if self.txn_id:
- #TODO: figure out the repo_path...
- repo_path = ''
- txn_id_file = get_txn_id_data_key(repo_path, self.txn_id)
- log.debug('Cleaning up TXN ID %s', txn_id_file)
- if os.path.isfile(txn_id_file):
- os.remove(txn_id_file)
-
- log.debug("Background thread done.")
-
- def _stop_gevent(self):
- log.debug("Waiting for background greenlet to finish.")
-
- # if greenlet exists and is running
- if self._callback_greenlet and not self._callback_greenlet.dead:
- # shutdown daemon if it exists
- if self._daemon:
- self._daemon.shutdown()
-
- # kill the greenlet
- self._callback_greenlet.kill()
-
- self._daemon = None
- self._callback_greenlet = None
-
- if self.txn_id:
- #TODO: figure out the repo_path...
- repo_path = ''
- txn_id_file = get_txn_id_data_key(repo_path, self.txn_id)
- log.debug('Cleaning up TXN ID %s', txn_id_file)
- if os.path.isfile(txn_id_file):
- os.remove(txn_id_file)
-
- log.debug("Background greenlet done.")
diff --git a/rhodecode/lib/hook_daemon/utils.py b/rhodecode/lib/hook_daemon/utils.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/hook_daemon/utils.py
@@ -0,0 +1,61 @@
+# Copyright (C) 2010-2024 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# 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 Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import time
+import logging
+
+from rhodecode.lib.config_utils import get_app_config_lightweight
+
+from rhodecode.lib.hook_daemon.base import Hooks
+from rhodecode.lib.hook_daemon.hook_module import HooksModuleCallbackDaemon
+from rhodecode.lib.hook_daemon.celery_hooks_deamon import CeleryHooksCallbackDaemon
+from rhodecode.lib.type_utils import str2bool
+
+log = logging.getLogger(__name__)
+
+
+
+def prepare_callback_daemon(extras, protocol: str, txn_id=None):
+ hooks_config = {}
+ match protocol:
+ case 'celery':
+ config = get_app_config_lightweight(extras['config'])
+
+ broker_url = config.get('celery.broker_url')
+ result_backend = config.get('celery.result_backend')
+
+ hooks_config = {
+ 'broker_url': broker_url,
+ 'result_backend': result_backend,
+ }
+
+ callback_daemon = CeleryHooksCallbackDaemon(broker_url, result_backend)
+ case 'local':
+ callback_daemon = HooksModuleCallbackDaemon(Hooks.__module__)
+ case _:
+ log.error('Unsupported callback daemon protocol "%s"', protocol)
+ raise Exception('Unsupported callback daemon protocol.')
+
+ extras['hooks_config'] = hooks_config
+ extras['hooks_protocol'] = protocol
+ extras['time'] = time.time()
+
+ # register txn_id
+ extras['txn_id'] = txn_id
+ log.debug('Prepared a callback daemon: %s', callback_daemon.__class__.__name__)
+ return callback_daemon, extras
diff --git a/rhodecode/lib/hooks_base.py b/rhodecode/lib/hooks_base.py
--- a/rhodecode/lib/hooks_base.py
+++ b/rhodecode/lib/hooks_base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 RhodeCode GmbH
+# Copyright (C) 2013-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -30,14 +30,14 @@ from rhodecode.lib import helpers as h
from rhodecode.lib import audit_logger
from rhodecode.lib.utils2 import safe_str, user_agent_normalizer
from rhodecode.lib.exceptions import (
- HTTPLockedRC, HTTPBranchProtected, UserCreationError, ClientNotSupportedError)
+ HTTPLockedRepo, HTTPBranchProtected, UserCreationError, ClientNotSupported)
from rhodecode.model.db import Repository, User
from rhodecode.lib.statsd_client import StatsdClient
log = logging.getLogger(__name__)
-class HookResponse(object):
+class HookResponse:
def __init__(self, status, output):
self.status = status
self.output = output
@@ -56,6 +56,8 @@ class HookResponse(object):
def to_json(self):
return {'status': self.status, 'output': self.output}
+ def __repr__(self):
+ return self.to_json().__repr__()
def is_shadow_repo(extras):
"""
@@ -73,8 +75,68 @@ def check_vcs_client(extras):
except ModuleNotFoundError:
is_vcs_client_whitelisted = lambda *x: True
backend = extras.get('scm')
- if not is_vcs_client_whitelisted(extras.get('user_agent'), backend):
- raise ClientNotSupportedError(f"Your {backend} client is forbidden")
+ user_agent = extras.get('user_agent')
+ if not is_vcs_client_whitelisted(user_agent, backend):
+ raise ClientNotSupported(f"Your {backend} client (version={user_agent}) is forbidden by security rules")
+
+
+def check_locked_repo(extras, check_same_user=True):
+ user = User.get_by_username(extras.username)
+ output = ''
+ if extras.locked_by[0] and (not check_same_user or user.user_id != extras.locked_by[0]):
+ locked_by = User.get(extras.locked_by[0]).username
+ reason = extras.locked_by[2]
+ # this exception is interpreted in git/hg middlewares and based
+ # on that proper return code is server to client
+ _http_ret = HTTPLockedRepo(_locked_by_explanation(extras.repository, locked_by, reason))
+ if str(_http_ret.code).startswith('2'):
+ # 2xx Codes don't raise exceptions
+ output = _http_ret.title
+ else:
+ raise _http_ret
+
+ return output
+
+
+def check_branch_protected(extras):
+ if extras.commit_ids and extras.check_branch_perms:
+ user = User.get_by_username(extras.username)
+ auth_user = user.AuthUser()
+ repo = Repository.get_by_repo_name(extras.repository)
+ if not repo:
+ raise ValueError(f'Repo for {extras.repository} not found')
+ affected_branches = []
+ if repo.repo_type == 'hg':
+ for entry in extras.commit_ids:
+ if entry['type'] == 'branch':
+ is_forced = bool(entry['multiple_heads'])
+ affected_branches.append([entry['name'], is_forced])
+ elif repo.repo_type == 'git':
+ for entry in extras.commit_ids:
+ if entry['type'] == 'heads':
+ is_forced = bool(entry['pruned_sha'])
+ affected_branches.append([entry['name'], is_forced])
+
+ for branch_name, is_forced in affected_branches:
+
+ rule, branch_perm = auth_user.get_rule_and_branch_permission(extras.repository, branch_name)
+ if not branch_perm:
+ # no branch permission found for this branch, just keep checking
+ continue
+
+ if branch_perm == 'branch.push_force':
+ continue
+ elif branch_perm == 'branch.push' and is_forced is False:
+ continue
+ elif branch_perm == 'branch.push' and is_forced is True:
+ halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}. ' \
+ f'FORCE PUSH FORBIDDEN.'
+ else:
+ halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}.'
+
+ if halt_message:
+ _http_ret = HTTPBranchProtected(halt_message)
+ raise _http_ret
def _get_scm_size(alias, root_path):
@@ -109,116 +171,30 @@ def repo_size(extras):
repo = Repository.get_by_repo_name(extras.repository)
vcs_part = f'.{repo.repo_type}'
size_vcs, size_root, size_total = _get_scm_size(vcs_part, repo.repo_full_path)
- msg = (f'RhodeCode: `{repo.repo_name}` size summary {vcs_part}:{size_vcs} repo:{size_root} total:{size_total}\n')
+ msg = f'RhodeCode: `{repo.repo_name}` size summary {vcs_part}:{size_vcs} repo:{size_root} total:{size_total}\n'
return HookResponse(0, msg)
-def pre_push(extras):
- """
- Hook executed before pushing code.
-
- It bans pushing when the repository is locked.
- """
-
- check_vcs_client(extras)
- user = User.get_by_username(extras.username)
- output = ''
- if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]):
- locked_by = User.get(extras.locked_by[0]).username
- reason = extras.locked_by[2]
- # this exception is interpreted in git/hg middlewares and based
- # on that proper return code is server to client
- _http_ret = HTTPLockedRC(
- _locked_by_explanation(extras.repository, locked_by, reason))
- if str(_http_ret.code).startswith('2'):
- # 2xx Codes don't raise exceptions
- output = _http_ret.title
- else:
- raise _http_ret
-
- hook_response = ''
- if not is_shadow_repo(extras):
-
- if extras.commit_ids and extras.check_branch_perms:
- auth_user = user.AuthUser()
- repo = Repository.get_by_repo_name(extras.repository)
- if not repo:
- raise ValueError(f'Repo for {extras.repository} not found')
- affected_branches = []
- if repo.repo_type == 'hg':
- for entry in extras.commit_ids:
- if entry['type'] == 'branch':
- is_forced = bool(entry['multiple_heads'])
- affected_branches.append([entry['name'], is_forced])
- elif repo.repo_type == 'git':
- for entry in extras.commit_ids:
- if entry['type'] == 'heads':
- is_forced = bool(entry['pruned_sha'])
- affected_branches.append([entry['name'], is_forced])
-
- for branch_name, is_forced in affected_branches:
-
- rule, branch_perm = auth_user.get_rule_and_branch_permission(
- extras.repository, branch_name)
- if not branch_perm:
- # no branch permission found for this branch, just keep checking
- continue
-
- if branch_perm == 'branch.push_force':
- continue
- elif branch_perm == 'branch.push' and is_forced is False:
- continue
- elif branch_perm == 'branch.push' and is_forced is True:
- halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}. ' \
- f'FORCE PUSH FORBIDDEN.'
- else:
- halt_message = f'Branch `{branch_name}` changes rejected by rule {rule}.'
-
- if halt_message:
- _http_ret = HTTPBranchProtected(halt_message)
- raise _http_ret
-
- # Propagate to external components. This is done after checking the
- # lock, for consistent behavior.
- hook_response = pre_push_extension(
- repo_store_path=Repository.base_path(), **extras)
- events.trigger(events.RepoPrePushEvent(
- repo_name=extras.repository, extras=extras))
-
- return HookResponse(0, output) + hook_response
-
-
def pre_pull(extras):
"""
Hook executed before pulling the code.
It bans pulling when the repository is locked.
+ It bans pulling when incorrect client is used.
"""
-
- check_vcs_client(extras)
output = ''
- if extras.locked_by[0]:
- locked_by = User.get(extras.locked_by[0]).username
- reason = extras.locked_by[2]
- # this exception is interpreted in git/hg middlewares and based
- # on that proper return code is server to client
- _http_ret = HTTPLockedRC(
- _locked_by_explanation(extras.repository, locked_by, reason))
- if str(_http_ret.code).startswith('2'):
- # 2xx Codes don't raise exceptions
- output = _http_ret.title
- else:
- raise _http_ret
+ check_vcs_client(extras)
+
+ # locking repo can, but not have to stop the operation it can also just produce output
+ output += check_locked_repo(extras, check_same_user=False)
# Propagate to external components. This is done after checking the
# lock, for consistent behavior.
hook_response = ''
if not is_shadow_repo(extras):
extras.hook_type = extras.hook_type or 'pre_pull'
- hook_response = pre_pull_extension(
- repo_store_path=Repository.base_path(), **extras)
- events.trigger(events.RepoPrePullEvent(
- repo_name=extras.repository, extras=extras))
+ hook_response = pre_pull_extension(repo_store_path=Repository.base_path(), **extras)
+ events.trigger(events.RepoPrePullEvent(repo_name=extras.repository, extras=extras))
return HookResponse(0, output) + hook_response
@@ -239,6 +215,7 @@ def post_pull(extras):
statsd.incr('rhodecode_pull_total', tags=[
f'user-agent:{user_agent_normalizer(extras.user_agent)}',
])
+
output = ''
# make lock is a tri state False, True, None. We only make lock on True
if extras.make_lock is True and not is_shadow_repo(extras):
@@ -246,18 +223,9 @@ def post_pull(extras):
Repository.lock(Repository.get_by_repo_name(extras.repository),
user.user_id,
lock_reason=Repository.LOCK_PULL)
- msg = 'Made lock on repo `{}`'.format(extras.repository)
+ msg = f'Made lock on repo `{extras.repository}`'
output += msg
- if extras.locked_by[0]:
- locked_by = User.get(extras.locked_by[0]).username
- reason = extras.locked_by[2]
- _http_ret = HTTPLockedRC(
- _locked_by_explanation(extras.repository, locked_by, reason))
- if str(_http_ret.code).startswith('2'):
- # 2xx Codes don't raise exceptions
- output += _http_ret.title
-
# Propagate to external components.
hook_response = ''
if not is_shadow_repo(extras):
@@ -270,6 +238,33 @@ def post_pull(extras):
return HookResponse(0, output) + hook_response
+def pre_push(extras):
+ """
+ Hook executed before pushing code.
+
+ It bans pushing when the repository is locked.
+ It banks pushing when incorrect client is used.
+ It also checks for Branch protection
+ """
+ output = ''
+ check_vcs_client(extras)
+
+ # locking repo can, but not have to stop the operation it can also just produce output
+ output += check_locked_repo(extras)
+
+ hook_response = ''
+ if not is_shadow_repo(extras):
+
+ check_branch_protected(extras)
+
+ # Propagate to external components. This is done after checking the
+ # lock, for consistent behavior.
+ hook_response = pre_push_extension(repo_store_path=Repository.base_path(), **extras)
+ events.trigger(events.RepoPrePushEvent(repo_name=extras.repository, extras=extras))
+
+ return HookResponse(0, output) + hook_response
+
+
def post_push(extras):
"""Hook executed after user pushes to the repository."""
commit_ids = extras.commit_ids
@@ -292,22 +287,13 @@ def post_push(extras):
# Propagate to external components.
output = ''
+
# make lock is a tri state False, True, None. We only release lock on False
if extras.make_lock is False and not is_shadow_repo(extras):
Repository.unlock(Repository.get_by_repo_name(extras.repository))
msg = f'Released lock on repo `{extras.repository}`\n'
output += msg
- if extras.locked_by[0]:
- locked_by = User.get(extras.locked_by[0]).username
- reason = extras.locked_by[2]
- _http_ret = HTTPLockedRC(
- _locked_by_explanation(extras.repository, locked_by, reason))
- # TODO: johbo: if not?
- if str(_http_ret.code).startswith('2'):
- # 2xx Codes don't raise exceptions
- output += _http_ret.title
-
if extras.new_refs:
tmpl = '{}/{}/pull-request/new?{{ref_type}}={{ref_name}}'.format(
safe_str(extras.server_url), safe_str(extras.repository))
@@ -322,11 +308,8 @@ def post_push(extras):
hook_response = ''
if not is_shadow_repo(extras):
- hook_response = post_push_extension(
- repo_store_path=Repository.base_path(),
- **extras)
- events.trigger(events.RepoPushEvent(
- repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras))
+ hook_response = post_push_extension(repo_store_path=Repository.base_path(), **extras)
+ events.trigger(events.RepoPushEvent(repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras))
output += 'RhodeCode: push completed\n'
return HookResponse(0, output) + hook_response
@@ -380,12 +363,20 @@ class ExtensionCallback(object):
# with older rcextensions that require api_key present
if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
kwargs_to_pass['api_key'] = '_DEPRECATED_'
- return callback(**kwargs_to_pass)
+ result = callback(**kwargs_to_pass)
+ log.debug('got rcextensions result: %s', result)
+ return result
def is_active(self):
return hasattr(rhodecode.EXTENSIONS, self._hook_name)
def _get_callback(self):
+ if rhodecode.is_test:
+ log.debug('In test mode, reloading rcextensions...')
+ # NOTE: for test re-load rcextensions always so we can dynamically change them for testing purposes
+ from rhodecode.lib.utils import load_rcextensions
+ load_rcextensions(root_path=os.path.dirname(rhodecode.CONFIG['__file__']))
+ return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
diff --git a/rhodecode/lib/hooks_utils.py b/rhodecode/lib/hooks_utils.py
--- a/rhodecode/lib/hooks_utils.py
+++ b/rhodecode/lib/hooks_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/html_filters.py b/rhodecode/lib/html_filters.py
--- a/rhodecode/lib/html_filters.py
+++ b/rhodecode/lib/html_filters.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2020-2023 RhodeCode GmbH
+# Copyright (C) 2020-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/index/__init__.py b/rhodecode/lib/index/__init__.py
--- a/rhodecode/lib/index/__init__.py
+++ b/rhodecode/lib/index/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/index/search_utils.py b/rhodecode/lib/index/search_utils.py
--- a/rhodecode/lib/index/search_utils.py
+++ b/rhodecode/lib/index/search_utils.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/index/whoosh.py b/rhodecode/lib/index/whoosh.py
--- a/rhodecode/lib/index/whoosh.py
+++ b/rhodecode/lib/index/whoosh.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/index/whoosh_fallback_schema.py b/rhodecode/lib/index/whoosh_fallback_schema.py
--- a/rhodecode/lib/index/whoosh_fallback_schema.py
+++ b/rhodecode/lib/index/whoosh_fallback_schema.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/jsonalchemy.py b/rhodecode/lib/jsonalchemy.py
--- a/rhodecode/lib/jsonalchemy.py
+++ b/rhodecode/lib/jsonalchemy.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/logging_formatter.py b/rhodecode/lib/logging_formatter.py
--- a/rhodecode/lib/logging_formatter.py
+++ b/rhodecode/lib/logging_formatter.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/markdown_ext.py b/rhodecode/lib/markdown_ext.py
--- a/rhodecode/lib/markdown_ext.py
+++ b/rhodecode/lib/markdown_ext.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/markup_renderer.py b/rhodecode/lib/markup_renderer.py
--- a/rhodecode/lib/markup_renderer.py
+++ b/rhodecode/lib/markup_renderer.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/memory_lru_dict.py b/rhodecode/lib/memory_lru_dict.py
--- a/rhodecode/lib/memory_lru_dict.py
+++ b/rhodecode/lib/memory_lru_dict.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/__init__.py b/rhodecode/lib/middleware/__init__.py
--- a/rhodecode/lib/middleware/__init__.py
+++ b/rhodecode/lib/middleware/__init__.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/appenlight.py b/rhodecode/lib/middleware/appenlight.py
--- a/rhodecode/lib/middleware/appenlight.py
+++ b/rhodecode/lib/middleware/appenlight.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/csrf.py b/rhodecode/lib/middleware/csrf.py
--- a/rhodecode/lib/middleware/csrf.py
+++ b/rhodecode/lib/middleware/csrf.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/https_fixup.py b/rhodecode/lib/middleware/https_fixup.py
--- a/rhodecode/lib/middleware/https_fixup.py
+++ b/rhodecode/lib/middleware/https_fixup.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/request_wrapper.py b/rhodecode/lib/middleware/request_wrapper.py
--- a/rhodecode/lib/middleware/request_wrapper.py
+++ b/rhodecode/lib/middleware/request_wrapper.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py
--- a/rhodecode/lib/middleware/simplegit.py
+++ b/rhodecode/lib/middleware/simplegit.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -40,16 +39,6 @@ GIT_PROTO_PAT = re.compile(
GIT_LFS_PROTO_PAT = re.compile(r'^/(.+)/(info/lfs/(.+))')
-def default_lfs_store():
- """
- Default lfs store location, it's consistent with Mercurials large file
- store which is in .cache/largefiles
- """
- from rhodecode.lib.vcs.backends.git import lfs_store
- user_home = os.path.expanduser("~")
- return lfs_store(user_home)
-
-
class SimpleGit(simplevcs.SimpleVCS):
SCM = 'git'
@@ -151,6 +140,6 @@ class SimpleGit(simplevcs.SimpleVCS):
extras['git_lfs_enabled'] = utils2.str2bool(
config.get('vcs_git_lfs', 'enabled'))
- extras['git_lfs_store_path'] = custom_store or default_lfs_store()
+ extras['git_lfs_store_path'] = custom_store
extras['git_lfs_http_scheme'] = scheme
return extras
diff --git a/rhodecode/lib/middleware/simplehg.py b/rhodecode/lib/middleware/simplehg.py
--- a/rhodecode/lib/middleware/simplehg.py
+++ b/rhodecode/lib/middleware/simplehg.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -22,6 +21,7 @@ SimpleHG middleware for handling mercuri
(push/clone etc.). It's implemented with basic auth function
"""
+import copy
import logging
import urllib.parse
import urllib.request
@@ -32,6 +32,7 @@ from rhodecode.lib import utils
from rhodecode.lib.ext_json import json
from rhodecode.lib.middleware import simplevcs
from rhodecode.lib.middleware.utils import get_path_info
+from rhodecode.lib.str_utils import safe_str
log = logging.getLogger(__name__)
@@ -95,7 +96,7 @@ class SimpleHg(simplevcs.SimpleVCS):
i = 1
chunks = [] # gather chunks stored in multiple 'hgarg_N'
while True:
- head = environ.get('HTTP_X_HGARG_{}'.format(i))
+ head = environ.get(f'HTTP_X_HGARG_{i}')
if not head:
break
i += 1
@@ -118,8 +119,18 @@ class SimpleHg(simplevcs.SimpleVCS):
"""
default = 'push'
batch_cmds = []
+
try:
- cmds = cls._get_xarg_headers(environ)
+ httppostargs_enabled = True
+ post_args_size = environ.get('HTTP_X_HGARGS_POST')
+ if post_args_size and httppostargs_enabled:
+ # a new proto when httppostargs is enabled
+ response_data = copy.copy(environ['wsgi.input'])
+ cmds = [safe_str(response_data.read(post_args_size))]
+ else:
+ # old way... from headers
+ cmds = cls._get_xarg_headers(environ)
+
for pair in cmds:
parts = pair.split(' ', 1)
if len(parts) != 2:
diff --git a/rhodecode/lib/middleware/simplesvn.py b/rhodecode/lib/middleware/simplesvn.py
--- a/rhodecode/lib/middleware/simplesvn.py
+++ b/rhodecode/lib/middleware/simplesvn.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/simplevcs.py b/rhodecode/lib/middleware/simplevcs.py
--- a/rhodecode/lib/middleware/simplevcs.py
+++ b/rhodecode/lib/middleware/simplevcs.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -32,8 +30,7 @@ from functools import wraps
import time
from paste.httpheaders import REMOTE_USER, AUTH_TYPE
-from pyramid.httpexceptions import (
- HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
+from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError
from zope.cachedescriptors.property import Lazy as LazyProperty
import rhodecode
@@ -41,10 +38,9 @@ from rhodecode.authentication.base impor
from rhodecode.lib import rc_cache
from rhodecode.lib.svn_txn_utils import store_txn_id_data
from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
-from rhodecode.lib.base import (
- BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
-from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
-from rhodecode.lib.hook_daemon.base import prepare_callback_daemon
+from rhodecode.lib.base import BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context
+from rhodecode.lib.exceptions import UserCreationError, NotAllowedToCreateUserError
+from rhodecode.lib.hook_daemon.utils import prepare_callback_daemon
from rhodecode.lib.middleware import appenlight
from rhodecode.lib.middleware.utils import scm_app_http
from rhodecode.lib.str_utils import safe_bytes, safe_int
@@ -78,17 +74,18 @@ def initialize_generator(factory):
try:
init = next(gen)
except StopIteration:
- raise ValueError('Generator must yield at least one element.')
+ raise ValueError("Generator must yield at least one element.")
if init != "__init__":
raise ValueError('First yielded element must be "__init__".')
return gen
+
return wrapper
class SimpleVCS(object):
"""Common functionality for SCM HTTP handlers."""
- SCM = 'unknown'
+ SCM = "unknown"
acl_repo_name = None
url_repo_name = None
@@ -100,11 +97,11 @@ class SimpleVCS(object):
# we use this regex which will match only on URLs pointing to shadow
# repositories.
shadow_repo_re = re.compile(
- '(?P(?:{slug_pat}/)*)' # repo groups
- '(?P{slug_pat})/' # target repo
- 'pull-request/(?P\\d+)/' # pull request
- 'repository$' # shadow repo
- .format(slug_pat=SLUG_RE.pattern))
+ "(?P(?:{slug_pat}/)*)" # repo groups
+ "(?P{slug_pat})/" # target repo
+ "pull-request/(?P\\d+)/" # pull request
+ "repository$".format(slug_pat=SLUG_RE.pattern) # shadow repo
+ )
def __init__(self, config, registry):
self.registry = registry
@@ -113,15 +110,14 @@ class SimpleVCS(object):
self.repo_vcs_config = base.Config()
rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False)
- realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH'
+ realm = rc_settings.get("rhodecode_realm") or "RhodeCode AUTH"
# authenticate this VCS request using authfunc
- auth_ret_code_detection = \
- str2bool(self.config.get('auth_ret_code_detection', False))
+ auth_ret_code_detection = str2bool(self.config.get("auth_ret_code_detection", False))
self.authenticate = BasicAuth(
- '', authenticate, registry, config.get('auth_ret_code'),
- auth_ret_code_detection, rc_realm=realm)
- self.ip_addr = '0.0.0.0'
+ "", authenticate, registry, config.get("auth_ret_code"), auth_ret_code_detection, rc_realm=realm
+ )
+ self.ip_addr = "0.0.0.0"
@LazyProperty
def global_vcs_config(self):
@@ -132,10 +128,10 @@ class SimpleVCS(object):
@property
def base_path(self):
- settings_path = self.config.get('repo_store.path')
+ settings_path = self.config.get("repo_store.path")
if not settings_path:
- raise ValueError('FATAL: repo_store.path is empty')
+ raise ValueError("FATAL: repo_store.path is empty")
return settings_path
def set_repo_names(self, environ):
@@ -164,17 +160,16 @@ class SimpleVCS(object):
match_dict = match.groupdict()
# Build acl repo name from regex match.
- acl_repo_name = safe_str('{groups}{target}'.format(
- groups=match_dict['groups'] or '',
- target=match_dict['target']))
+ acl_repo_name = safe_str(
+ "{groups}{target}".format(groups=match_dict["groups"] or "", target=match_dict["target"])
+ )
# Retrieve pull request instance by ID from regex match.
- pull_request = PullRequest.get(match_dict['pr_id'])
+ pull_request = PullRequest.get(match_dict["pr_id"])
# Only proceed if we got a pull request and if acl repo name from
# URL equals the target repo name of the pull request.
if pull_request and (acl_repo_name == pull_request.target_repo.repo_name):
-
# Get file system path to shadow repository.
workspace_id = PullRequestModel()._workspace_id(pull_request)
vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
@@ -184,21 +179,23 @@ class SimpleVCS(object):
self.acl_repo_name = acl_repo_name
self.is_shadow_repo = True
- log.debug('Setting all VCS repository names: %s', {
- 'acl_repo_name': self.acl_repo_name,
- 'url_repo_name': self.url_repo_name,
- 'vcs_repo_name': self.vcs_repo_name,
- })
+ log.debug(
+ "Setting all VCS repository names: %s",
+ {
+ "acl_repo_name": self.acl_repo_name,
+ "url_repo_name": self.url_repo_name,
+ "vcs_repo_name": self.vcs_repo_name,
+ },
+ )
@property
def scm_app(self):
- custom_implementation = self.config['vcs.scm_app_implementation']
- if custom_implementation == 'http':
- log.debug('Using HTTP implementation of scm app.')
+ custom_implementation = self.config["vcs.scm_app_implementation"]
+ if custom_implementation == "http":
+ log.debug("Using HTTP implementation of scm app.")
scm_app_impl = scm_app_http
else:
- log.debug('Using custom implementation of scm_app: "{}"'.format(
- custom_implementation))
+ log.debug('Using custom implementation of scm_app: "{}"'.format(custom_implementation))
scm_app_impl = importlib.import_module(custom_implementation)
return scm_app_impl
@@ -208,17 +205,18 @@ class SimpleVCS(object):
with a repository_name for support of _ non changeable urls
"""
- data = repo_name.split('/')
+ data = repo_name.split("/")
if len(data) >= 2:
from rhodecode.model.repo import RepoModel
+
by_id_match = RepoModel().get_repo_by_id(repo_name)
if by_id_match:
data[1] = by_id_match.repo_name
# Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO
# and we use this data
- maybe_new_path = '/'.join(data)
- return safe_bytes(maybe_new_path).decode('latin1')
+ maybe_new_path = "/".join(data)
+ return safe_bytes(maybe_new_path).decode("latin1")
def _invalidate_cache(self, repo_name):
"""
@@ -231,21 +229,18 @@ class SimpleVCS(object):
def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
db_repo = Repository.get_by_repo_name(repo_name)
if not db_repo:
- log.debug('Repository `%s` not found inside the database.',
- repo_name)
+ log.debug("Repository `%s` not found inside the database.", repo_name)
return False
if db_repo.repo_type != scm_type:
log.warning(
- 'Repository `%s` have incorrect scm_type, expected %s got %s',
- repo_name, db_repo.repo_type, scm_type)
+ "Repository `%s` have incorrect scm_type, expected %s got %s", repo_name, db_repo.repo_type, scm_type
+ )
return False
config = db_repo._config
- config.set('extensions', 'largefiles', '')
- return is_valid_repo(
- repo_name, base_path,
- explicit_scm=scm_type, expect_scm=scm_type, config=config)
+ config.set("extensions", "largefiles", "")
+ return is_valid_repo(repo_name, base_path, explicit_scm=scm_type, expect_scm=scm_type, config=config)
def valid_and_active_user(self, user):
"""
@@ -267,8 +262,9 @@ class SimpleVCS(object):
def is_shadow_repo_dir(self):
return os.path.isdir(self.vcs_repo_name)
- def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
- plugin_id='', plugin_cache_active=False, cache_ttl=0):
+ def _check_permission(
+ self, action, user, auth_user, repo_name, ip_addr=None, plugin_id="", plugin_cache_active=False, cache_ttl=0
+ ):
"""
Checks permissions using action (push/pull) user and repository
name. If plugin_cache and ttl is set it will use the plugin which
@@ -280,71 +276,67 @@ class SimpleVCS(object):
:param repo_name: repository name
"""
- log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
- plugin_id, plugin_cache_active, cache_ttl)
+ log.debug("AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)", plugin_id, plugin_cache_active, cache_ttl)
user_id = user.user_id
- cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}'
- region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
+ cache_namespace_uid = f"cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}"
+ region = rc_cache.get_or_create_region("cache_perms", cache_namespace_uid)
- @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
- expiration_time=cache_ttl,
- condition=plugin_cache_active)
- def compute_perm_vcs(
- cache_name, plugin_id, action, user_id, repo_name, ip_addr):
-
- log.debug('auth: calculating permission access now for vcs operation: %s', action)
+ @region.conditional_cache_on_arguments(
+ namespace=cache_namespace_uid, expiration_time=cache_ttl, condition=plugin_cache_active
+ )
+ def compute_perm_vcs(cache_name, plugin_id, action, user_id, repo_name, ip_addr):
+ log.debug("auth: calculating permission access now for vcs operation: %s", action)
# check IP
inherit = user.inherit_default_permissions
- ip_allowed = AuthUser.check_ip_allowed(
- user_id, ip_addr, inherit_from_default=inherit)
+ ip_allowed = AuthUser.check_ip_allowed(user_id, ip_addr, inherit_from_default=inherit)
if ip_allowed:
- log.info('Access for IP:%s allowed', ip_addr)
+ log.info("Access for IP:%s allowed", ip_addr)
else:
return False
- if action == 'push':
- perms = ('repository.write', 'repository.admin')
+ if action == "push":
+ perms = ("repository.write", "repository.admin")
if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
return False
else:
# any other action need at least read permission
- perms = (
- 'repository.read', 'repository.write', 'repository.admin')
+ perms = ("repository.read", "repository.write", "repository.admin")
if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
return False
return True
start = time.time()
- log.debug('Running plugin `%s` permissions check', plugin_id)
+ log.debug("Running plugin `%s` permissions check", plugin_id)
# for environ based auth, password can be empty, but then the validation is
# on the server that fills in the env data needed for authentication
- perm_result = compute_perm_vcs(
- 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
+ perm_result = compute_perm_vcs("vcs_permissions", plugin_id, action, user.user_id, repo_name, ip_addr)
auth_time = time.time() - start
- log.debug('Permissions for plugin `%s` completed in %.4fs, '
- 'expiration time of fetched cache %.1fs.',
- plugin_id, auth_time, cache_ttl)
+ log.debug(
+ "Permissions for plugin `%s` completed in %.4fs, " "expiration time of fetched cache %.1fs.",
+ plugin_id,
+ auth_time,
+ cache_ttl,
+ )
return perm_result
def _get_http_scheme(self, environ):
try:
- return environ['wsgi.url_scheme']
+ return environ["wsgi.url_scheme"]
except Exception:
- log.exception('Failed to read http scheme')
- return 'http'
+ log.exception("Failed to read http scheme")
+ return "http"
def _get_default_cache_ttl(self):
# take AUTH_CACHE_TTL from the `rhodecode` auth plugin
- plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
+ plugin = loadplugin("egg:rhodecode-enterprise-ce#rhodecode")
plugin_settings = plugin.get_settings()
- plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
- plugin_settings) or (False, 0)
+ plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings) or (False, 0)
return plugin_cache_active, cache_ttl
def __call__(self, environ, start_response):
@@ -359,17 +351,17 @@ class SimpleVCS(object):
def _handle_request(self, environ, start_response):
if not self.url_repo_name:
- log.warning('Repository name is empty: %s', self.url_repo_name)
+ log.warning("Repository name is empty: %s", self.url_repo_name)
# failed to get repo name, we fail now
return HTTPNotFound()(environ, start_response)
- log.debug('Extracted repo name is %s', self.url_repo_name)
+ log.debug("Extracted repo name is %s", self.url_repo_name)
ip_addr = get_ip_addr(environ)
user_agent = get_user_agent(environ)
username = None
# skip passing error to error controller
- environ['pylons.status_code_redirect'] = True
+ environ["pylons.status_code_redirect"] = True
# ======================================================================
# GET ACTION PULL or PUSH
@@ -380,17 +372,15 @@ class SimpleVCS(object):
# Check if this is a request to a shadow repository of a pull request.
# In this case only pull action is allowed.
# ======================================================================
- if self.is_shadow_repo and action != 'pull':
- reason = 'Only pull action is allowed for shadow repositories.'
- log.debug('User not allowed to proceed, %s', reason)
+ if self.is_shadow_repo and action != "pull":
+ reason = "Only pull action is allowed for shadow repositories."
+ log.debug("User not allowed to proceed, %s", reason)
return HTTPNotAcceptable(reason)(environ, start_response)
# Check if the shadow repo actually exists, in case someone refers
# to it, and it has been deleted because of successful merge.
if self.is_shadow_repo and not self.is_shadow_repo_dir:
- log.debug(
- 'Shadow repo detected, and shadow repo dir `%s` is missing',
- self.is_shadow_repo_dir)
+ log.debug("Shadow repo detected, and shadow repo dir `%s` is missing", self.is_shadow_repo_dir)
return HTTPNotFound()(environ, start_response)
# ======================================================================
@@ -398,7 +388,7 @@ class SimpleVCS(object):
# ======================================================================
detect_force_push = False
check_branch_perms = False
- if action in ['pull', 'push']:
+ if action in ["pull", "push"]:
user_obj = anonymous_user = User.get_default_user()
auth_user = user_obj.AuthUser()
username = anonymous_user.username
@@ -406,8 +396,12 @@ class SimpleVCS(object):
plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
# ONLY check permissions if the user is activated
anonymous_perm = self._check_permission(
- action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
- plugin_id='anonymous_access',
+ action,
+ anonymous_user,
+ auth_user,
+ self.acl_repo_name,
+ ip_addr,
+ plugin_id="anonymous_access",
plugin_cache_active=plugin_cache_active,
cache_ttl=cache_ttl,
)
@@ -416,12 +410,13 @@ class SimpleVCS(object):
if not anonymous_user.active or not anonymous_perm:
if not anonymous_user.active:
- log.debug('Anonymous access is disabled, running '
- 'authentication')
+ log.debug("Anonymous access is disabled, running " "authentication")
if not anonymous_perm:
- log.debug('Not enough credentials to access repo: `%s` '
- 'repository as anonymous user', self.acl_repo_name)
+ log.debug(
+ "Not enough credentials to access repo: `%s` " "repository as anonymous user",
+ self.acl_repo_name,
+ )
username = None
# ==============================================================
@@ -430,19 +425,18 @@ class SimpleVCS(object):
# ==============================================================
# try to auth based on environ, container auth methods
- log.debug('Running PRE-AUTH for container|headers based authentication')
+ log.debug("Running PRE-AUTH for container|headers based authentication")
# headers auth, by just reading special headers and bypass the auth with user/passwd
pre_auth = authenticate(
- '', '', environ, VCS_TYPE, registry=self.registry,
- acl_repo_name=self.acl_repo_name)
+ "", "", environ, VCS_TYPE, registry=self.registry, acl_repo_name=self.acl_repo_name
+ )
- if pre_auth and pre_auth.get('username'):
- username = pre_auth['username']
- log.debug('PRE-AUTH got `%s` as username', username)
+ if pre_auth and pre_auth.get("username"):
+ username = pre_auth["username"]
+ log.debug("PRE-AUTH got `%s` as username", username)
if pre_auth:
- log.debug('PRE-AUTH successful from %s',
- pre_auth.get('auth_data', {}).get('_plugin'))
+ log.debug("PRE-AUTH successful from %s", pre_auth.get("auth_data", {}).get("_plugin"))
# If not authenticated by the container, running basic auth
# before inject the calling repo_name for special scope checks
@@ -463,16 +457,16 @@ class SimpleVCS(object):
return HTTPNotAcceptable(reason)(environ, start_response)
if isinstance(auth_result, dict):
- AUTH_TYPE.update(environ, 'basic')
- REMOTE_USER.update(environ, auth_result['username'])
- username = auth_result['username']
- plugin = auth_result.get('auth_data', {}).get('_plugin')
- log.info(
- 'MAIN-AUTH successful for user `%s` from %s plugin',
- username, plugin)
+ AUTH_TYPE.update(environ, "basic")
+ REMOTE_USER.update(environ, auth_result["username"])
+ username = auth_result["username"]
+ plugin = auth_result.get("auth_data", {}).get("_plugin")
+ log.info("MAIN-AUTH successful for user `%s` from %s plugin", username, plugin)
- plugin_cache_active, cache_ttl = auth_result.get(
- 'auth_data', {}).get('_ttl_cache') or (False, 0)
+ plugin_cache_active, cache_ttl = auth_result.get("auth_data", {}).get("_ttl_cache") or (
+ False,
+ 0,
+ )
else:
return auth_result.wsgi_application(environ, start_response)
@@ -488,21 +482,24 @@ class SimpleVCS(object):
# check user attributes for password change flag
user_obj = user
auth_user = user_obj.AuthUser()
- if user_obj and user_obj.username != User.DEFAULT_USER and \
- user_obj.user_data.get('force_password_change'):
- reason = 'password change required'
- log.debug('User not allowed to authenticate, %s', reason)
+ if (
+ user_obj
+ and user_obj.username != User.DEFAULT_USER
+ and user_obj.user_data.get("force_password_change")
+ ):
+ reason = "password change required"
+ log.debug("User not allowed to authenticate, %s", reason)
return HTTPNotAcceptable(reason)(environ, start_response)
# check permissions for this repository
perm = self._check_permission(
- action, user, auth_user, self.acl_repo_name, ip_addr,
- plugin, plugin_cache_active, cache_ttl)
+ action, user, auth_user, self.acl_repo_name, ip_addr, plugin, plugin_cache_active, cache_ttl
+ )
if not perm:
return HTTPForbidden()(environ, start_response)
- environ['rc_auth_user_id'] = str(user_id)
+ environ["rc_auth_user_id"] = str(user_id)
- if action == 'push':
+ if action == "push":
perms = auth_user.get_branch_permissions(self.acl_repo_name)
if perms:
check_branch_perms = True
@@ -510,41 +507,48 @@ class SimpleVCS(object):
# extras are injected into UI object and later available
# in hooks executed by RhodeCode
- check_locking = _should_check_locking(environ.get('QUERY_STRING'))
+ check_locking = _should_check_locking(environ.get("QUERY_STRING"))
extras = vcs_operation_context(
- environ, repo_name=self.acl_repo_name, username=username,
- action=action, scm=self.SCM, check_locking=check_locking,
- is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
- detect_force_push=detect_force_push
+ environ,
+ repo_name=self.acl_repo_name,
+ username=username,
+ action=action,
+ scm=self.SCM,
+ check_locking=check_locking,
+ is_shadow_repo=self.is_shadow_repo,
+ check_branch_perms=check_branch_perms,
+ detect_force_push=detect_force_push,
)
# ======================================================================
# REQUEST HANDLING
# ======================================================================
- repo_path = os.path.join(
- safe_str(self.base_path), safe_str(self.vcs_repo_name))
- log.debug('Repository path is %s', repo_path)
+ repo_path = os.path.join(safe_str(self.base_path), safe_str(self.vcs_repo_name))
+ log.debug("Repository path is %s", repo_path)
fix_PATH()
log.info(
'%s action on %s repo "%s" by "%s" from %s %s',
- action, self.SCM, safe_str(self.url_repo_name),
- safe_str(username), ip_addr, user_agent)
+ action,
+ self.SCM,
+ safe_str(self.url_repo_name),
+ safe_str(username),
+ ip_addr,
+ user_agent,
+ )
- return self._generate_vcs_response(
- environ, start_response, repo_path, extras, action)
+ return self._generate_vcs_response(environ, start_response, repo_path, extras, action)
def _get_txn_id(self, environ):
-
- for k in ['RAW_URI', 'HTTP_DESTINATION']:
+ for k in ["RAW_URI", "HTTP_DESTINATION"]:
url = environ.get(k)
if not url:
continue
# regex to search for svn-txn-id
- pattern = r'/!svn/txr/([^/]+)/'
+ pattern = r"/!svn/txr/([^/]+)/"
# Search for the pattern in the URL
match = re.search(pattern, url)
@@ -555,8 +559,7 @@ class SimpleVCS(object):
return txn_id
@initialize_generator
- def _generate_vcs_response(
- self, environ, start_response, repo_path, extras, action):
+ def _generate_vcs_response(self, environ, start_response, repo_path, extras, action):
"""
Returns a generator for the response content.
@@ -565,24 +568,20 @@ class SimpleVCS(object):
also handles the locking exceptions which will be triggered when
the first chunk is produced by the underlying WSGI application.
"""
- svn_txn_id = ''
- if action == 'push':
+ svn_txn_id = ""
+ if action == "push":
svn_txn_id = self._get_txn_id(environ)
- callback_daemon, extras = self._prepare_callback_daemon(
- extras, environ, action, txn_id=svn_txn_id)
+ callback_daemon, extras = self._prepare_callback_daemon(extras, environ, action, txn_id=svn_txn_id)
if svn_txn_id:
-
- port = safe_int(extras['hooks_uri'].split(':')[-1])
txn_id_data = extras.copy()
- txn_id_data.update({'port': port})
- txn_id_data.update({'req_method': environ['REQUEST_METHOD']})
+ txn_id_data.update({"req_method": environ["REQUEST_METHOD"]})
full_repo_path = repo_path
store_txn_id_data(full_repo_path, svn_txn_id, txn_id_data)
- log.debug('HOOKS extras is %s', extras)
+ log.debug("HOOKS extras is %s", extras)
http_scheme = self._get_http_scheme(environ)
@@ -609,7 +608,7 @@ class SimpleVCS(object):
try:
# invalidate cache on push
- if action == 'push':
+ if action == "push":
self._invalidate_cache(self.url_repo_name)
finally:
meta.Session.remove()
@@ -632,12 +631,12 @@ class SimpleVCS(object):
"""Return the WSGI app that will finally handle the request."""
raise NotImplementedError()
- def _create_config(self, extras, repo_name, scheme='http'):
+ def _create_config(self, extras, repo_name, scheme="http"):
"""Create a safe config representation."""
raise NotImplementedError()
def _should_use_callback_daemon(self, extras, environ, action):
- if extras.get('is_shadow_repo'):
+ if extras.get("is_shadow_repo"):
# we don't want to execute hooks, and callback daemon for shadow repos
return False
return True
@@ -647,11 +646,9 @@ class SimpleVCS(object):
if not self._should_use_callback_daemon(extras, environ, action):
# disable callback daemon for actions that don't require it
- protocol = 'local'
+ protocol = "local"
- return prepare_callback_daemon(
- extras, protocol=protocol,
- host=vcs_settings.HOOKS_HOST, txn_id=txn_id)
+ return prepare_callback_daemon(extras, protocol=protocol, txn_id=txn_id)
def _should_check_locking(query_string):
@@ -659,4 +656,4 @@ def _should_check_locking(query_string):
# server see all operation on commit; bookmarks, phases and
# obsolescence marker in different transaction, we don't want to check
# locking on those
- return query_string not in ['cmd=listkeys']
+ return query_string not in ["cmd=listkeys"]
diff --git a/rhodecode/lib/middleware/utils/__init__.py b/rhodecode/lib/middleware/utils/__init__.py
--- a/rhodecode/lib/middleware/utils/__init__.py
+++ b/rhodecode/lib/middleware/utils/__init__.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/utils/scm_app.py b/rhodecode/lib/middleware/utils/scm_app.py
--- a/rhodecode/lib/middleware/utils/scm_app.py
+++ b/rhodecode/lib/middleware/utils/scm_app.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/utils/scm_app_http.py b/rhodecode/lib/middleware/utils/scm_app_http.py
--- a/rhodecode/lib/middleware/utils/scm_app_http.py
+++ b/rhodecode/lib/middleware/utils/scm_app_http.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/utils/wsgi_app_caller_client.py b/rhodecode/lib/middleware/utils/wsgi_app_caller_client.py
--- a/rhodecode/lib/middleware/utils/wsgi_app_caller_client.py
+++ b/rhodecode/lib/middleware/utils/wsgi_app_caller_client.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/middleware/vcs.py b/rhodecode/lib/middleware/vcs.py
--- a/rhodecode/lib/middleware/vcs.py
+++ b/rhodecode/lib/middleware/vcs.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/pagination.py b/rhodecode/lib/pagination.py
--- a/rhodecode/lib/pagination.py
+++ b/rhodecode/lib/pagination.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (c) 2007-2012 Christoph Haas
+# Copyright (C) 2007-2012 Christoph Haas
# NOTE: MIT license based code, backported and edited by RhodeCode GmbH
"""
@@ -869,8 +867,7 @@ class _Page(list):
return make_html_tag("a", text=text, href=target_url, **item["attrs"])
-# Below is RhodeCode custom code
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Below is RhodeCode custom code# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/partial_renderer.py b/rhodecode/lib/partial_renderer.py
--- a/rhodecode/lib/partial_renderer.py
+++ b/rhodecode/lib/partial_renderer.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/pidlock.py b/rhodecode/lib/pidlock.py
--- a/rhodecode/lib/pidlock.py
+++ b/rhodecode/lib/pidlock.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/plugins/__init__.py b/rhodecode/lib/plugins/__init__.py
--- a/rhodecode/lib/plugins/__init__.py
+++ b/rhodecode/lib/plugins/__init__.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/plugins/utils.py b/rhodecode/lib/plugins/utils.py
--- a/rhodecode/lib/plugins/utils.py
+++ b/rhodecode/lib/plugins/utils.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/pyramid_shell/__init__.py b/rhodecode/lib/pyramid_shell/__init__.py
--- a/rhodecode/lib/pyramid_shell/__init__.py
+++ b/rhodecode/lib/pyramid_shell/__init__.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/pyramid_utils.py b/rhodecode/lib/pyramid_utils.py
--- a/rhodecode/lib/pyramid_utils.py
+++ b/rhodecode/lib/pyramid_utils.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/rc_cache/__init__.py b/rhodecode/lib/rc_cache/__init__.py
--- a/rhodecode/lib/rc_cache/__init__.py
+++ b/rhodecode/lib/rc_cache/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2023 RhodeCode GmbH
+# Copyright (C) 2015-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -55,7 +55,7 @@ register_backend(
log = logging.getLogger(__name__)
-FILE_TREE_CACHE_VER = 'v5'
+FILE_TREE_CACHE_VER = 'v6'
LICENSE_CACHE_VER = 'v3'
PERMISSIONS_CACHE_VER = 'v2'
diff --git a/rhodecode/lib/rc_cache/backends.py b/rhodecode/lib/rc_cache/backends.py
--- a/rhodecode/lib/rc_cache/backends.py
+++ b/rhodecode/lib/rc_cache/backends.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2023 RhodeCode GmbH
+# Copyright (C) 2015-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -216,6 +216,9 @@ class BaseRedisBackend(redis_backend.Red
self._lock_timeout = self.lock_timeout
self._lock_auto_renewal = str2bool(arguments.pop("lock_auto_renewal", True))
+ self._store_key_prefix = arguments.pop('key_prefix', '')
+ self.key_prefix = f'{self._store_key_prefix}{self.key_prefix}'
+
if self._lock_auto_renewal and not self._lock_timeout:
# set default timeout for auto_renewal
self._lock_timeout = 30
@@ -275,7 +278,7 @@ class BaseRedisBackend(redis_backend.Red
def get_mutex(self, key):
if self.distributed_lock:
- lock_key = f'_lock_{safe_str(key)}'
+ lock_key = f'{self._store_key_prefix}_lock_{safe_str(key)}'
return get_mutex_lock(
self.writer_client, lock_key,
self._lock_timeout,
diff --git a/rhodecode/lib/rc_cache/region_meta.py b/rhodecode/lib/rc_cache/region_meta.py
--- a/rhodecode/lib/rc_cache/region_meta.py
+++ b/rhodecode/lib/rc_cache/region_meta.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015-2023 RhodeCode GmbH
+# Copyright (C) 2015-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/rc_cache/utils.py b/rhodecode/lib/rc_cache/utils.py
--- a/rhodecode/lib/rc_cache/utils.py
+++ b/rhodecode/lib/rc_cache/utils.py
@@ -69,19 +69,17 @@ class RhodeCodeCacheRegion(CacheRegion):
def get_or_create_for_user_func(func_key_generator, user_func, *arg, **kw):
if not condition:
- log.debug('Calling un-cached method:%s', user_func.__name__)
+ log.debug('Calling un-cached method:`%s`', user_func.__name__)
start = time.time()
result = user_func(*arg, **kw)
total = time.time() - start
- log.debug('un-cached method:%s took %.4fs', user_func.__name__, total)
+ log.debug('Call for un-cached method:`%s` took %.4fs', user_func.__name__, total)
return result
key = func_key_generator(*arg, **kw)
+ timeout = expiration_time() if expiration_time_is_callable else expiration_time
+ log.debug('Calling cached (timeout=%s) method:`%s`', timeout, user_func.__name__)
- timeout = expiration_time() if expiration_time_is_callable \
- else expiration_time
-
- log.debug('Calling cached method:`%s`', user_func.__name__)
return self.get_or_create(key, user_func, timeout, should_cache_fn, (arg, kw))
def cache_decorator(user_func):
@@ -174,7 +172,7 @@ def backend_key_generator(backend):
return wrapper
-def get_or_create_region(region_name, region_namespace: str = None, use_async_runner=False):
+def get_or_create_region(region_name, region_namespace: str = None, use_async_runner=False, force=False):
from .backends import FileNamespaceBackend
from . import async_creation_runner
@@ -191,7 +189,7 @@ def get_or_create_region(region_name, re
raise ValueError(f'{FileNamespaceBackend} used requires to specify region_namespace param')
region_exist = region_meta.dogpile_cache_regions.get(region_namespace)
- if region_exist:
+ if region_exist and not force:
log.debug('Using already configured region: %s', region_namespace)
return region_exist
diff --git a/rhodecode/lib/rc_commands/add_artifact.py b/rhodecode/lib/rc_commands/add_artifact.py
--- a/rhodecode/lib/rc_commands/add_artifact.py
+++ b/rhodecode/lib/rc_commands/add_artifact.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/rc_commands/ishell.py b/rhodecode/lib/rc_commands/ishell.py
--- a/rhodecode/lib/rc_commands/ishell.py
+++ b/rhodecode/lib/rc_commands/ishell.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/rc_commands/migrate_artifact.py b/rhodecode/lib/rc_commands/migrate_artifact.py
--- a/rhodecode/lib/rc_commands/migrate_artifact.py
+++ b/rhodecode/lib/rc_commands/migrate_artifact.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/rc_commands/setup_rc.py b/rhodecode/lib/rc_commands/setup_rc.py
--- a/rhodecode/lib/rc_commands/setup_rc.py
+++ b/rhodecode/lib/rc_commands/setup_rc.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/rc_commands/upgrade_db.py b/rhodecode/lib/rc_commands/upgrade_db.py
--- a/rhodecode/lib/rc_commands/upgrade_db.py
+++ b/rhodecode/lib/rc_commands/upgrade_db.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/repo_maintenance.py b/rhodecode/lib/repo_maintenance.py
--- a/rhodecode/lib/repo_maintenance.py
+++ b/rhodecode/lib/repo_maintenance.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/request.py b/rhodecode/lib/request.py
--- a/rhodecode/lib/request.py
+++ b/rhodecode/lib/request.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/request_counter.py b/rhodecode/lib/request_counter.py
--- a/rhodecode/lib/request_counter.py
+++ b/rhodecode/lib/request_counter.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/str_utils.py b/rhodecode/lib/str_utils.py
--- a/rhodecode/lib/str_utils.py
+++ b/rhodecode/lib/str_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -67,10 +67,7 @@ def base64_to_str(text: str | bytes) ->
def get_default_encodings() -> list[str]:
- return aslist(rhodecode.CONFIG.get('default_encoding', 'utf8'), sep=',')
-
-
-DEFAULT_ENCODINGS = get_default_encodings()
+ return rhodecode.ConfigGet().get_list('default_encoding', missing='utf8')
def safe_str(str_, to_encoding=None) -> str:
@@ -87,7 +84,7 @@ def safe_str(str_, to_encoding=None) ->
if not isinstance(str_, bytes):
return str(str_)
- to_encoding = to_encoding or DEFAULT_ENCODINGS
+ to_encoding = to_encoding or get_default_encodings()
if not isinstance(to_encoding, (list, tuple)):
to_encoding = [to_encoding]
@@ -120,7 +117,7 @@ def safe_bytes(str_, from_encoding=None)
for enc in from_encoding:
try:
return str_.encode(enc)
- except UnicodeDecodeError:
+ except (UnicodeDecodeError, UnicodeEncodeError):
pass
return str_.encode(from_encoding[0], 'replace')
diff --git a/rhodecode/lib/string_renderer.py b/rhodecode/lib/string_renderer.py
--- a/rhodecode/lib/string_renderer.py
+++ b/rhodecode/lib/string_renderer.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/svn_txn_utils.py b/rhodecode/lib/svn_txn_utils.py
--- a/rhodecode/lib/svn_txn_utils.py
+++ b/rhodecode/lib/svn_txn_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/system_info.py b/rhodecode/lib/system_info.py
--- a/rhodecode/lib/system_info.py
+++ b/rhodecode/lib/system_info.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/type_utils.py b/rhodecode/lib/type_utils.py
--- a/rhodecode/lib/type_utils.py
+++ b/rhodecode/lib/type_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/user_log_filter.py b/rhodecode/lib/user_log_filter.py
--- a/rhodecode/lib/user_log_filter.py
+++ b/rhodecode/lib/user_log_filter.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/user_sessions.py b/rhodecode/lib/user_sessions.py
--- a/rhodecode/lib/user_sessions.py
+++ b/rhodecode/lib/user_sessions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py
--- a/rhodecode/lib/utils.py
+++ b/rhodecode/lib/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,6 +21,7 @@ Utilities library for RhodeCode
"""
import datetime
+import importlib
import decorator
import logging
@@ -42,8 +43,9 @@ from webhelpers2.text import collapse, s
from mako import exceptions
+import rhodecode
from rhodecode import ConfigGet
-from rhodecode.lib.exceptions import HTTPBranchProtected, HTTPLockedRC
+from rhodecode.lib.exceptions import HTTPBranchProtected, HTTPLockedRepo, ClientNotSupported
from rhodecode.lib.hash_utils import sha256_safe, md5, sha1
from rhodecode.lib.type_utils import AttributeDict
from rhodecode.lib.str_utils import safe_bytes, safe_str
@@ -86,6 +88,7 @@ def adopt_for_celery(func):
@wraps(func)
def wrapper(extras):
extras = AttributeDict(extras)
+
try:
# HooksResponse implements to_json method which must be used there.
return func(extras).to_json()
@@ -100,7 +103,18 @@ def adopt_for_celery(func):
'exception_args': error_args,
'exception_traceback': '',
}
- except HTTPLockedRC as error:
+ except ClientNotSupported as error:
+ # Those special cases don't need error reporting. It's a case of
+ # locked repo or protected branch
+ error_args = error.args
+ return {
+ 'status': error.code,
+ 'output': error.explanation,
+ 'exception': type(error).__name__,
+ 'exception_args': error_args,
+ 'exception_traceback': '',
+ }
+ except HTTPLockedRepo as error:
# Those special cases don't need error reporting. It's a case of
# locked repo or protected branch
error_args = error.args
@@ -117,7 +131,7 @@ def adopt_for_celery(func):
'output': '',
'exception': type(e).__name__,
'exception_args': e.args,
- 'exception_traceback': '',
+ 'exception_traceback': traceback.format_exc(),
}
return wrapper
@@ -411,6 +425,10 @@ def prepare_config_data(clear_session=Tr
('web', 'push_ssl', 'false'),
]
for setting in ui_settings:
+ # skip certain deprecated keys that might be still in DB
+ if f"{setting.section}_{setting.key}" in ['extensions_hgsubversion']:
+ continue
+
# Todo: remove this section once transition to *.ini files will be completed
if setting.section in ('largefiles', 'vcs_git_lfs'):
if setting.key != 'enabled':
@@ -564,23 +582,19 @@ def map_groups(path):
return group
-def repo2db_mapper(initial_repo_list, remove_obsolete=False, force_hooks_rebuild=False):
+def repo2db_mapper(initial_repo_list, force_hooks_rebuild=False):
"""
- maps all repos given in initial_repo_list, non existing repositories
- are created, if remove_obsolete is True it also checks for db entries
- that are not in initial_repo_list and removes them.
-
- :param initial_repo_list: list of repositories found by scanning methods
- :param remove_obsolete: check for obsolete entries in database
+ maps all repos given in initial_repo_list, non-existing repositories
+ are created
"""
from rhodecode.model.repo import RepoModel
- from rhodecode.model.repo_group import RepoGroupModel
from rhodecode.model.settings import SettingsModel
sa = meta.Session()
repo_model = RepoModel()
user = User.get_first_super_admin()
added = []
+ errors = []
# creation defaults
defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
@@ -598,9 +612,7 @@ def repo2db_mapper(initial_repo_list, re
if not db_repo:
log.info('repository `%s` not found in the database, creating now', name)
added.append(name)
- desc = (repo.description
- if repo.description != 'unknown'
- else '%s repository' % name)
+ desc = repo.description if repo.description != 'unknown' else f'{name} repository'
db_repo = repo_model._create_repo(
repo_name=name,
@@ -615,93 +627,151 @@ def repo2db_mapper(initial_repo_list, re
state=Repository.STATE_CREATED
)
sa.commit()
+
+ try:
+ config = db_repo._config
+ config.set('extensions', 'largefiles', '')
+ scm_repo = db_repo.scm_instance(config=config)
+ except Exception:
+ log.error(traceback.format_exc())
+ errors.append(f'getting vcs instance for {name} failed')
+ continue
+
+ try:
+ db_repo.update_commit_cache(recursive=False)
+ except Exception:
+ log.error(traceback.format_exc())
+ errors.append(f'update_commit_cache for {name} failed')
+ continue
+
+ try:
+ scm_repo.install_hooks(force=force_hooks_rebuild)
+ except Exception:
+ log.error(traceback.format_exc())
+ errors.append(f'install_hooks for {name} failed')
+ continue
+
+ try:
# we added that repo just now, and make sure we updated server info
if db_repo.repo_type == 'git':
- git_repo = db_repo.scm_instance()
# update repository server-info
log.debug('Running update server info')
- git_repo._update_server_info(force=True)
-
- db_repo.update_commit_cache(recursive=False)
+ scm_repo._update_server_info(force=True)
+ except Exception:
+ log.error(traceback.format_exc())
+ errors.append(f'update_server_info for {name} failed')
+ continue
- config = db_repo._config
- config.set('extensions', 'largefiles', '')
- repo = db_repo.scm_instance(config=config)
- repo.install_hooks(force=force_hooks_rebuild)
+ return added, errors
+def repo2db_cleanup(skip_repos=None, skip_groups=None):
+ from rhodecode.model.repo import RepoModel
+ from rhodecode.model.repo_group import RepoGroupModel
+
+ sa = meta.Session()
removed = []
- if remove_obsolete:
- # remove from database those repositories that are not in the filesystem
- for repo in sa.query(Repository).all():
- if repo.repo_name not in list(initial_repo_list.keys()):
- log.debug("Removing non-existing repository found in db `%s`",
- repo.repo_name)
- try:
- RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
- sa.commit()
- removed.append(repo.repo_name)
- except Exception:
- # don't hold further removals on error
- log.error(traceback.format_exc())
- sa.rollback()
+ errors = []
+
+
+ all_repos = Repository.execute(
+ Repository.select(Repository)\
+ .order_by(Repository.repo_name)
+ ).scalars()
- def splitter(full_repo_name):
- _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
- gr_name = None
- if len(_parts) == 2:
- gr_name = _parts[0]
- return gr_name
+ # remove from database those repositories that are not in the filesystem
+ for db_repo in all_repos:
+ db_repo_name = db_repo.repo_name
+ if skip_repos and db_repo_name in skip_repos:
+ log.debug('Skipping repo `%s`', db_repo_name)
+ continue
+ try:
+ instance = db_repo.scm_instance()
+ except Exception:
+ instance = None
+
+ if not instance:
+ log.debug("Removing non-existing repository found in db `%s`", db_repo_name)
+ try:
+ RepoModel(sa).delete(db_repo, forks='detach', fs_remove=False, call_events=False)
+ sa.commit()
+ removed.append(db_repo_name)
+ except Exception:
+ # don't hold further removals on error
+ log.error(traceback.format_exc())
+ sa.rollback()
+ errors.append(db_repo_name)
- initial_repo_group_list = [splitter(x) for x in
- list(initial_repo_list.keys()) if splitter(x)]
+ # remove from database those repository groups that are not in the
+ # filesystem due to parent child relationships we need to delete them
+ # in a specific order of most nested first
+ all_groups = RepoGroup.execute(
+ RepoGroup.select(RepoGroup.group_name)\
+ .order_by(RepoGroup.group_name)
+ ).scalars().all()
+
+ def nested_sort(gr):
+ return len(gr.split('/'))
- # remove from database those repository groups that are not in the
- # filesystem due to parent child relationships we need to delete them
- # in a specific order of most nested first
- all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
- def nested_sort(gr):
- return len(gr.split('/'))
- for group_name in sorted(all_groups, key=nested_sort, reverse=True):
- if group_name not in initial_repo_group_list:
- repo_group = RepoGroup.get_by_group_name(group_name)
- if (repo_group.children.all() or
- not RepoGroupModel().check_exist_filesystem(
- group_name=group_name, exc_on_failure=False)):
- continue
+ for group_name in sorted(all_groups, key=nested_sort, reverse=True):
+ if skip_groups and group_name in skip_groups:
+ log.debug('Skipping repo group `%s`', group_name)
+ continue
+
+ repo_group = RepoGroup.get_by_group_name(group_name)
+
+ if repo_group.children.all() or not RepoGroupModel().check_exist_filesystem(group_name=group_name, exc_on_failure=False):
+ continue
+
+ log.info('Removing non-existing repository group found in db `%s`', group_name)
- log.info(
- 'Removing non-existing repository group found in db `%s`',
- group_name)
- try:
- RepoGroupModel(sa).delete(group_name, fs_remove=False)
- sa.commit()
- removed.append(group_name)
- except Exception:
- # don't hold further removals on error
- log.exception(
- 'Unable to remove repository group `%s`',
- group_name)
- sa.rollback()
- raise
+ try:
+ RepoGroupModel(sa).delete(group_name, fs_remove=False, call_events=False)
+ sa.commit()
+ removed.append(group_name)
+ except Exception:
+ # don't hold further removals on error
+ log.exception('Unable to remove repository group `%s`',group_name)
+ sa.rollback()
+ errors.append(group_name)
+
+ return removed, errors
+
- return added, removed
+def deep_reload_package(package_name):
+ """
+ Deeply reload a package by removing it and its submodules from sys.modules,
+ then re-importing it.
+ """
+ # Remove the package and its submodules from sys.modules
+ to_reload = [name for name in sys.modules if name == package_name or name.startswith(package_name + ".")]
+ for module_name in to_reload:
+ del sys.modules[module_name]
+ log.debug(f"Removed module from cache: {module_name}")
+ # Re-import the package
+ package = importlib.import_module(package_name)
+ log.debug(f"Re-imported package: {package_name}")
+
+ return package
def load_rcextensions(root_path):
import rhodecode
from rhodecode.config import conf
path = os.path.join(root_path)
- sys.path.append(path)
+ deep_reload = path in sys.path
+ sys.path.insert(0, path)
try:
- rcextensions = __import__('rcextensions')
+ rcextensions = __import__('rcextensions', fromlist=[''])
except ImportError:
if os.path.isdir(os.path.join(path, 'rcextensions')):
log.warning('Unable to load rcextensions from %s', path)
rcextensions = None
if rcextensions:
+ if deep_reload:
+ rcextensions = deep_reload_package('rcextensions')
log.info('Loaded rcextensions from %s...', rcextensions)
rhodecode.EXTENSIONS = rcextensions
@@ -741,6 +811,7 @@ def create_test_index(repo_location, con
except ImportError:
raise ImportError('Failed to import rc_testdata, '
'please make sure this package is installed from requirements_test.txt')
+
rc_testdata.extract_search_index(
'vcs_search_index', os.path.dirname(config['search.location']))
@@ -785,22 +856,15 @@ def create_test_repositories(test_path,
Creates test repositories in the temporary directory. Repositories are
extracted from archives within the rc_testdata package.
"""
- import rc_testdata
- from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
-
- log.debug('making test vcs repositories')
-
- idx_path = config['search.location']
- data_path = config['cache_dir']
+ try:
+ import rc_testdata
+ except ImportError:
+ raise ImportError('Failed to import rc_testdata, '
+ 'please make sure this package is installed from requirements_test.txt')
- # clean index and data
- if idx_path and os.path.exists(idx_path):
- log.debug('remove %s', idx_path)
- shutil.rmtree(idx_path)
+ from rhodecode.bootstrap import HG_REPO, GIT_REPO, SVN_REPO
- if data_path and os.path.exists(data_path):
- log.debug('remove %s', data_path)
- shutil.rmtree(data_path)
+ log.debug('making test vcs repositories at %s', test_path)
rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
diff --git a/rhodecode/lib/utils2.py b/rhodecode/lib/utils2.py
--- a/rhodecode/lib/utils2.py
+++ b/rhodecode/lib/utils2.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -153,6 +153,7 @@ def remove_prefix(s, prefix):
def find_calling_context(ignore_modules=None, depth=4, output_writer=None, indent=True):
"""
+ How to find calling context:
Look through the calling stack and return the frame which called
this function and is part of core module ( ie. rhodecode.* )
diff --git a/rhodecode/lib/vcs/__init__.py b/rhodecode/lib/vcs/__init__.py
--- a/rhodecode/lib/vcs/__init__.py
+++ b/rhodecode/lib/vcs/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -139,8 +139,8 @@ class CurlSession(object):
try:
curl.perform()
- except pycurl.error as exc:
- log.error('Failed to call endpoint url: {} using pycurl'.format(url))
+ except pycurl.error:
+ log.error('Failed to call endpoint url: %s using pycurl', url)
raise
status_code = curl.getinfo(pycurl.HTTP_CODE)
diff --git a/rhodecode/lib/vcs/backends/__init__.py b/rhodecode/lib/vcs/backends/__init__.py
--- a/rhodecode/lib/vcs/backends/__init__.py
+++ b/rhodecode/lib/vcs/backends/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -58,9 +58,10 @@ def get_vcs_instance(repo_path, *args, *
raise VCSError(f"Given path {repo_path} is not a directory")
except VCSError:
log.exception(
- 'Perhaps this repository is in db and not in '
- 'filesystem run rescan repositories with '
- '"destroy old data" option from admin panel')
+ 'Perhaps this repository is in db and not in filesystem.'
+ 'Run cleanup filesystem option from admin settings under Remap and rescan'
+ )
+
return None
return backend(repo_path=repo_path, *args, **kwargs)
diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py
--- a/rhodecode/lib/vcs/backends/base.py
+++ b/rhodecode/lib/vcs/backends/base.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -19,6 +19,7 @@
"""
Base module for all VCS systems
"""
+
import os
import re
import time
@@ -39,19 +40,25 @@ from rhodecode.lib.utils2 import safe_st
from rhodecode.lib.vcs.utils import author_name, author_email
from rhodecode.lib.vcs.conf import settings
from rhodecode.lib.vcs.exceptions import (
- CommitError, EmptyRepositoryError, NodeAlreadyAddedError,
- NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
- NodeDoesNotExistError, NodeNotChangedError, VCSError,
- ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError,
- RepositoryError)
+ CommitError,
+ EmptyRepositoryError,
+ NodeAlreadyAddedError,
+ NodeAlreadyChangedError,
+ NodeAlreadyExistsError,
+ NodeAlreadyRemovedError,
+ NodeDoesNotExistError,
+ NodeNotChangedError,
+ VCSError,
+ ImproperArchiveTypeError,
+ BranchDoesNotExistError,
+ CommitDoesNotExistError,
+ RepositoryError,
+)
log = logging.getLogger(__name__)
-
-FILEMODE_DEFAULT = 0o100644
-FILEMODE_EXECUTABLE = 0o100755
-EMPTY_COMMIT_ID = '0' * 40
+EMPTY_COMMIT_ID = "0" * 40
@dataclasses.dataclass
@@ -67,12 +74,12 @@ class Reference:
@property
def branch(self):
- if self.type == 'branch':
+ if self.type == "branch":
return self.name
@property
def bookmark(self):
- if self.type == 'book':
+ if self.type == "book":
return self.name
@property
@@ -80,11 +87,7 @@ class Reference:
return reference_to_unicode(self)
def asdict(self):
- return dict(
- type=self.type,
- name=self.name,
- commit_id=self.commit_id
- )
+ return dict(type=self.type, name=self.name, commit_id=self.commit_id)
def unicode_to_reference(raw: str):
@@ -93,7 +96,7 @@ def unicode_to_reference(raw: str):
If unicode evaluates to False it returns None.
"""
if raw:
- refs = raw.split(':')
+ refs = raw.split(":")
return Reference(*refs)
else:
return None
@@ -105,7 +108,7 @@ def reference_to_unicode(ref: Reference)
If reference is None it returns None.
"""
if ref:
- return ':'.join(ref)
+ return ":".join(ref)
else:
return None
@@ -194,47 +197,44 @@ class UpdateFailureReason(object):
class MergeResponse(object):
-
# uses .format(**metadata) for variables
MERGE_STATUS_MESSAGES = {
- MergeFailureReason.NONE: lazy_ugettext(
- 'This pull request can be automatically merged.'),
+ MergeFailureReason.NONE: lazy_ugettext("This pull request can be automatically merged."),
MergeFailureReason.UNKNOWN: lazy_ugettext(
- 'This pull request cannot be merged because of an unhandled exception. '
- '{exception}'),
+ "This pull request cannot be merged because of an unhandled exception. " "{exception}"
+ ),
MergeFailureReason.MERGE_FAILED: lazy_ugettext(
- 'This pull request cannot be merged because of merge conflicts. {unresolved_files}'),
+ "This pull request cannot be merged because of merge conflicts. {unresolved_files}"
+ ),
MergeFailureReason.PUSH_FAILED: lazy_ugettext(
- 'This pull request could not be merged because push to '
- 'target:`{target}@{merge_commit}` failed.'),
+ "This pull request could not be merged because push to " "target:`{target}@{merge_commit}` failed."
+ ),
MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext(
- 'This pull request cannot be merged because the target '
- '`{target_ref.name}` is not a head.'),
+ "This pull request cannot be merged because the target " "`{target_ref.name}` is not a head."
+ ),
MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext(
- 'This pull request cannot be merged because the source contains '
- 'more branches than the target.'),
+ "This pull request cannot be merged because the source contains " "more branches than the target."
+ ),
MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext(
- 'This pull request cannot be merged because the target `{target_ref.name}` '
- 'has multiple heads: `{heads}`.'),
+ "This pull request cannot be merged because the target `{target_ref.name}` "
+ "has multiple heads: `{heads}`."
+ ),
MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext(
- 'This pull request cannot be merged because the target repository is '
- 'locked by {locked_by}.'),
-
+ "This pull request cannot be merged because the target repository is " "locked by {locked_by}."
+ ),
MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext(
- 'This pull request cannot be merged because the target '
- 'reference `{target_ref.name}` is missing.'),
+ "This pull request cannot be merged because the target " "reference `{target_ref.name}` is missing."
+ ),
MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
- 'This pull request cannot be merged because the source '
- 'reference `{source_ref.name}` is missing.'),
+ "This pull request cannot be merged because the source " "reference `{source_ref.name}` is missing."
+ ),
MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext(
- 'This pull request cannot be merged because of conflicts related '
- 'to sub repositories.'),
-
+ "This pull request cannot be merged because of conflicts related " "to sub repositories."
+ ),
# Deprecations
MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext(
- 'This pull request cannot be merged because the target or the '
- 'source reference is missing.'),
-
+ "This pull request cannot be merged because the target or the " "source reference is missing."
+ ),
}
def __init__(self, possible, executed, merge_ref: Reference, failure_reason, metadata=None):
@@ -245,19 +245,20 @@ class MergeResponse(object):
self.metadata = metadata or {}
def __repr__(self):
- return f''
+ return f""
def __eq__(self, other):
same_instance = isinstance(other, self.__class__)
- return same_instance \
- and self.possible == other.possible \
- and self.executed == other.executed \
- and self.failure_reason == other.failure_reason
+ return (
+ same_instance
+ and self.possible == other.possible
+ and self.executed == other.executed
+ and self.failure_reason == other.failure_reason
+ )
@property
def label(self):
- label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if
- not k.startswith('_'))
+ label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if not k.startswith("_"))
return label_dict.get(self.failure_reason)
@property
@@ -270,13 +271,12 @@ class MergeResponse(object):
try:
return msg.format(**self.metadata)
except Exception:
- log.exception('Failed to format %s message', self)
+ log.exception("Failed to format %s message", self)
return msg
def asdict(self):
data = {}
- for k in ['possible', 'executed', 'merge_ref', 'failure_reason',
- 'merge_status_message']:
+ for k in ["possible", "executed", "merge_ref", "failure_reason", "merge_status_message"]:
data[k] = getattr(self, k)
return data
@@ -289,607 +289,7 @@ class SourceRefMissing(ValueError):
pass
-class BaseRepository(object):
- """
- Base Repository for final backends
-
- .. attribute:: DEFAULT_BRANCH_NAME
-
- name of default branch (i.e. "trunk" for svn, "master" for git etc.
-
- .. attribute:: commit_ids
-
- list of all available commit ids, in ascending order
-
- .. attribute:: path
-
- absolute path to the repository
-
- .. attribute:: bookmarks
-
- Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
- there are no bookmarks or the backend implementation does not support
- bookmarks.
-
- .. attribute:: tags
-
- Mapping from name to :term:`Commit ID` of the tag.
-
- """
-
- DEFAULT_BRANCH_NAME = None
- DEFAULT_CONTACT = "Unknown"
- DEFAULT_DESCRIPTION = "unknown"
- EMPTY_COMMIT_ID = '0' * 40
- COMMIT_ID_PAT = re.compile(r'[0-9a-fA-F]{40}')
-
- path = None
-
- _is_empty = None
- _commit_ids = {}
-
- def __init__(self, repo_path, config=None, create=False, **kwargs):
- """
- Initializes repository. Raises RepositoryError if repository could
- not be find at the given ``repo_path`` or directory at ``repo_path``
- exists and ``create`` is set to True.
-
- :param repo_path: local path of the repository
- :param config: repository configuration
- :param create=False: if set to True, would try to create repository.
- :param src_url=None: if set, should be proper url from which repository
- would be cloned; requires ``create`` parameter to be set to True -
- raises RepositoryError if src_url is set and create evaluates to
- False
- """
- raise NotImplementedError
-
- def __repr__(self):
- return f'<{self.__class__.__name__} at {self.path}>'
-
- def __len__(self):
- return self.count()
-
- def __eq__(self, other):
- same_instance = isinstance(other, self.__class__)
- return same_instance and other.path == self.path
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def get_create_shadow_cache_pr_path(self, db_repo):
- path = db_repo.cached_diffs_dir
- if not os.path.exists(path):
- os.makedirs(path, 0o755)
- return path
-
- @classmethod
- def get_default_config(cls, default=None):
- config = Config()
- if default and isinstance(default, list):
- for section, key, val in default:
- config.set(section, key, val)
- return config
-
- @LazyProperty
- def _remote(self):
- raise NotImplementedError
-
- def _heads(self, branch=None):
- return []
-
- @LazyProperty
- def EMPTY_COMMIT(self):
- return EmptyCommit(self.EMPTY_COMMIT_ID)
-
- @LazyProperty
- def alias(self):
- for k, v in settings.BACKENDS.items():
- if v.split('.')[-1] == str(self.__class__.__name__):
- return k
-
- @LazyProperty
- def name(self):
- return safe_str(os.path.basename(self.path))
-
- @LazyProperty
- def description(self):
- raise NotImplementedError
-
- def refs(self):
- """
- returns a `dict` with branches, bookmarks, tags, and closed_branches
- for this repository
- """
- return dict(
- branches=self.branches,
- branches_closed=self.branches_closed,
- tags=self.tags,
- bookmarks=self.bookmarks
- )
-
- @LazyProperty
- def branches(self):
- """
- A `dict` which maps branch names to commit ids.
- """
- raise NotImplementedError
-
- @LazyProperty
- def branches_closed(self):
- """
- A `dict` which maps tags names to commit ids.
- """
- raise NotImplementedError
-
- @LazyProperty
- def bookmarks(self):
- """
- A `dict` which maps tags names to commit ids.
- """
- raise NotImplementedError
-
- @LazyProperty
- def tags(self):
- """
- A `dict` which maps tags names to commit ids.
- """
- raise NotImplementedError
-
- @LazyProperty
- def size(self):
- """
- Returns combined size in bytes for all repository files
- """
- tip = self.get_commit()
- return tip.size
-
- def size_at_commit(self, commit_id):
- commit = self.get_commit(commit_id)
- return commit.size
-
- def _check_for_empty(self):
- no_commits = len(self._commit_ids) == 0
- if no_commits:
- # check on remote to be sure
- return self._remote.is_empty()
- else:
- return False
-
- def is_empty(self):
- if rhodecode.is_test:
- return self._check_for_empty()
-
- if self._is_empty is None:
- # cache empty for production, but not tests
- self._is_empty = self._check_for_empty()
-
- return self._is_empty
-
- @staticmethod
- def check_url(url, config):
- """
- Function will check given url and try to verify if it's a valid
- link.
- """
- raise NotImplementedError
-
- @staticmethod
- def is_valid_repository(path):
- """
- Check if given `path` contains a valid repository of this backend
- """
- raise NotImplementedError
-
- # ==========================================================================
- # COMMITS
- # ==========================================================================
-
- @CachedProperty
- def commit_ids(self):
- raise NotImplementedError
-
- def append_commit_id(self, commit_id):
- if commit_id not in self.commit_ids:
- self._rebuild_cache(self.commit_ids + [commit_id])
-
- # clear cache
- self._invalidate_prop_cache('commit_ids')
- self._is_empty = False
-
- def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
- translate_tag=None, maybe_unreachable=False, reference_obj=None):
- """
- Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
- are both None, most recent commit is returned.
-
- :param pre_load: Optional. List of commit attributes to load.
-
- :raises ``EmptyRepositoryError``: if there are no commits
- """
- raise NotImplementedError
-
- def __iter__(self):
- for commit_id in self.commit_ids:
- yield self.get_commit(commit_id=commit_id)
-
- def get_commits(
- self, start_id=None, end_id=None, start_date=None, end_date=None,
- branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
- """
- Returns iterator of `BaseCommit` objects from start to end
- not inclusive. This should behave just like a list, ie. end is not
- inclusive.
-
- :param start_id: None or str, must be a valid commit id
- :param end_id: None or str, must be a valid commit id
- :param start_date:
- :param end_date:
- :param branch_name:
- :param show_hidden:
- :param pre_load:
- :param translate_tags:
- """
- raise NotImplementedError
-
- def __getitem__(self, key):
- """
- Allows index based access to the commit objects of this repository.
- """
- pre_load = ["author", "branch", "date", "message", "parents"]
- if isinstance(key, slice):
- return self._get_range(key, pre_load)
- return self.get_commit(commit_idx=key, pre_load=pre_load)
-
- def _get_range(self, slice_obj, pre_load):
- for commit_id in self.commit_ids.__getitem__(slice_obj):
- yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
-
- def count(self):
- return len(self.commit_ids)
-
- def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
- """
- Creates and returns a tag for the given ``commit_id``.
-
- :param name: name for new tag
- :param user: full username, i.e.: "Joe Doe "
- :param commit_id: commit id for which new tag would be created
- :param message: message of the tag's commit
- :param date: date of tag's commit
-
- :raises TagAlreadyExistError: if tag with same name already exists
- """
- raise NotImplementedError
-
- def remove_tag(self, name, user, message=None, date=None):
- """
- Removes tag with the given ``name``.
-
- :param name: name of the tag to be removed
- :param user: full username, i.e.: "Joe Doe "
- :param message: message of the tag's removal commit
- :param date: date of tag's removal commit
-
- :raises TagDoesNotExistError: if tag with given name does not exists
- """
- raise NotImplementedError
-
- def get_diff(
- self, commit1, commit2, path=None, ignore_whitespace=False,
- context=3, path1=None):
- """
- Returns (git like) *diff*, as plain text. Shows changes introduced by
- `commit2` since `commit1`.
-
- :param commit1: Entry point from which diff is shown. Can be
- ``self.EMPTY_COMMIT`` - in this case, patch showing all
- the changes since empty state of the repository until `commit2`
- :param commit2: Until which commit changes should be shown.
- :param path: Can be set to a path of a file to create a diff of that
- file. If `path1` is also set, this value is only associated to
- `commit2`.
- :param ignore_whitespace: If set to ``True``, would not show whitespace
- changes. Defaults to ``False``.
- :param context: How many lines before/after changed lines should be
- shown. Defaults to ``3``.
- :param path1: Can be set to a path to associate with `commit1`. This
- parameter works only for backends which support diff generation for
- different paths. Other backends will raise a `ValueError` if `path1`
- is set and has a different value than `path`.
- :param file_path: filter this diff by given path pattern
- """
- raise NotImplementedError
-
- def strip(self, commit_id, branch=None):
- """
- Strip given commit_id from the repository
- """
- raise NotImplementedError
-
- def get_common_ancestor(self, commit_id1, commit_id2, repo2):
- """
- Return a latest common ancestor commit if one exists for this repo
- `commit_id1` vs `commit_id2` from `repo2`.
-
- :param commit_id1: Commit it from this repository to use as a
- target for the comparison.
- :param commit_id2: Source commit id to use for comparison.
- :param repo2: Source repository to use for comparison.
- """
- raise NotImplementedError
-
- def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
- """
- Compare this repository's revision `commit_id1` with `commit_id2`.
-
- Returns a tuple(commits, ancestor) that would be merged from
- `commit_id2`. Doing a normal compare (``merge=False``), ``None``
- will be returned as ancestor.
-
- :param commit_id1: Commit it from this repository to use as a
- target for the comparison.
- :param commit_id2: Source commit id to use for comparison.
- :param repo2: Source repository to use for comparison.
- :param merge: If set to ``True`` will do a merge compare which also
- returns the common ancestor.
- :param pre_load: Optional. List of commit attributes to load.
- """
- raise NotImplementedError
-
- def merge(self, repo_id, workspace_id, target_ref, source_repo, source_ref,
- user_name='', user_email='', message='', dry_run=False,
- use_rebase=False, close_branch=False):
- """
- Merge the revisions specified in `source_ref` from `source_repo`
- onto the `target_ref` of this repository.
-
- `source_ref` and `target_ref` are named tupls with the following
- fields `type`, `name` and `commit_id`.
-
- Returns a MergeResponse named tuple with the following fields
- 'possible', 'executed', 'source_commit', 'target_commit',
- 'merge_commit'.
-
- :param repo_id: `repo_id` target repo id.
- :param workspace_id: `workspace_id` unique identifier.
- :param target_ref: `target_ref` points to the commit on top of which
- the `source_ref` should be merged.
- :param source_repo: The repository that contains the commits to be
- merged.
- :param source_ref: `source_ref` points to the topmost commit from
- the `source_repo` which should be merged.
- :param user_name: Merge commit `user_name`.
- :param user_email: Merge commit `user_email`.
- :param message: Merge commit `message`.
- :param dry_run: If `True` the merge will not take place.
- :param use_rebase: If `True` commits from the source will be rebased
- on top of the target instead of being merged.
- :param close_branch: If `True` branch will be close before merging it
- """
- if dry_run:
- message = message or settings.MERGE_DRY_RUN_MESSAGE
- user_email = user_email or settings.MERGE_DRY_RUN_EMAIL
- user_name = user_name or settings.MERGE_DRY_RUN_USER
- else:
- if not user_name:
- raise ValueError('user_name cannot be empty')
- if not user_email:
- raise ValueError('user_email cannot be empty')
- if not message:
- raise ValueError('message cannot be empty')
-
- try:
- return self._merge_repo(
- repo_id, workspace_id, target_ref, source_repo,
- source_ref, message, user_name, user_email, dry_run=dry_run,
- use_rebase=use_rebase, close_branch=close_branch)
- except RepositoryError as exc:
- log.exception('Unexpected failure when running merge, dry-run=%s', dry_run)
- return MergeResponse(
- False, False, None, MergeFailureReason.UNKNOWN,
- metadata={'exception': str(exc)})
-
- def _merge_repo(self, repo_id, workspace_id, target_ref,
- source_repo, source_ref, merge_message,
- merger_name, merger_email, dry_run=False,
- use_rebase=False, close_branch=False):
- """Internal implementation of merge."""
- raise NotImplementedError
-
- def _maybe_prepare_merge_workspace(
- self, repo_id, workspace_id, target_ref, source_ref):
- """
- Create the merge workspace.
-
- :param workspace_id: `workspace_id` unique identifier.
- """
- raise NotImplementedError
-
- @classmethod
- def _get_legacy_shadow_repository_path(cls, repo_path, workspace_id):
- """
- Legacy version that was used before. We still need it for
- backward compat
- """
- return os.path.join(
- os.path.dirname(repo_path),
- f'.__shadow_{os.path.basename(repo_path)}_{workspace_id}')
-
- @classmethod
- def _get_shadow_repository_path(cls, repo_path, repo_id, workspace_id):
- # The name of the shadow repository must start with '.', so it is
- # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
- legacy_repository_path = cls._get_legacy_shadow_repository_path(repo_path, workspace_id)
- if os.path.exists(legacy_repository_path):
- return legacy_repository_path
- else:
- return os.path.join(
- os.path.dirname(repo_path),
- f'.__shadow_repo_{repo_id}_{workspace_id}')
-
- def cleanup_merge_workspace(self, repo_id, workspace_id):
- """
- Remove merge workspace.
-
- This function MUST not fail in case there is no workspace associated to
- the given `workspace_id`.
-
- :param workspace_id: `workspace_id` unique identifier.
- """
- shadow_repository_path = self._get_shadow_repository_path(
- self.path, repo_id, workspace_id)
- shadow_repository_path_del = '{}.{}.delete'.format(
- shadow_repository_path, time.time())
-
- # move the shadow repo, so it never conflicts with the one used.
- # we use this method because shutil.rmtree had some edge case problems
- # removing symlinked repositories
- if not os.path.isdir(shadow_repository_path):
- return
-
- shutil.move(shadow_repository_path, shadow_repository_path_del)
- try:
- shutil.rmtree(shadow_repository_path_del, ignore_errors=False)
- except Exception:
- log.exception('Failed to gracefully remove shadow repo under %s',
- shadow_repository_path_del)
- shutil.rmtree(shadow_repository_path_del, ignore_errors=True)
-
- # ========== #
- # COMMIT API #
- # ========== #
-
- @LazyProperty
- def in_memory_commit(self):
- """
- Returns :class:`InMemoryCommit` object for this repository.
- """
- raise NotImplementedError
-
- # ======================== #
- # UTILITIES FOR SUBCLASSES #
- # ======================== #
-
- def _validate_diff_commits(self, commit1, commit2):
- """
- Validates that the given commits are related to this repository.
-
- Intended as a utility for sub classes to have a consistent validation
- of input parameters in methods like :meth:`get_diff`.
- """
- self._validate_commit(commit1)
- self._validate_commit(commit2)
- if (isinstance(commit1, EmptyCommit) and
- isinstance(commit2, EmptyCommit)):
- raise ValueError("Cannot compare two empty commits")
-
- def _validate_commit(self, commit):
- if not isinstance(commit, BaseCommit):
- raise TypeError(
- "%s is not of type BaseCommit" % repr(commit))
- if commit.repository != self and not isinstance(commit, EmptyCommit):
- raise ValueError(
- "Commit %s must be a valid commit from this repository %s, "
- "related to this repository instead %s." %
- (commit, self, commit.repository))
-
- def _validate_commit_id(self, commit_id):
- if not isinstance(commit_id, str):
- raise TypeError(f"commit_id must be a string value got {type(commit_id)} instead")
-
- def _validate_commit_idx(self, commit_idx):
- if not isinstance(commit_idx, int):
- raise TypeError(f"commit_idx must be a numeric value, got {type(commit_idx)}")
-
- def _validate_branch_name(self, branch_name):
- if branch_name and branch_name not in self.branches_all:
- msg = (f"Branch {branch_name} not found in {self}")
- raise BranchDoesNotExistError(msg)
-
- #
- # Supporting deprecated API parts
- # TODO: johbo: consider to move this into a mixin
- #
-
- @property
- def EMPTY_CHANGESET(self):
- warnings.warn(
- "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
- return self.EMPTY_COMMIT_ID
-
- @property
- def revisions(self):
- warnings.warn("Use commits attribute instead", DeprecationWarning)
- return self.commit_ids
-
- @revisions.setter
- def revisions(self, value):
- warnings.warn("Use commits attribute instead", DeprecationWarning)
- self.commit_ids = value
-
- def get_changeset(self, revision=None, pre_load=None):
- warnings.warn("Use get_commit instead", DeprecationWarning)
- commit_id = None
- commit_idx = None
- if isinstance(revision, str):
- commit_id = revision
- else:
- commit_idx = revision
- return self.get_commit(
- commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
-
- def get_changesets(
- self, start=None, end=None, start_date=None, end_date=None,
- branch_name=None, pre_load=None):
- warnings.warn("Use get_commits instead", DeprecationWarning)
- start_id = self._revision_to_commit(start)
- end_id = self._revision_to_commit(end)
- return self.get_commits(
- start_id=start_id, end_id=end_id, start_date=start_date,
- end_date=end_date, branch_name=branch_name, pre_load=pre_load)
-
- def _revision_to_commit(self, revision):
- """
- Translates a revision to a commit_id
-
- Helps to support the old changeset based API which allows to use
- commit ids and commit indices interchangeable.
- """
- if revision is None:
- return revision
-
- if isinstance(revision, str):
- commit_id = revision
- else:
- commit_id = self.commit_ids[revision]
- return commit_id
-
- @property
- def in_memory_changeset(self):
- warnings.warn("Use in_memory_commit instead", DeprecationWarning)
- return self.in_memory_commit
-
- def get_path_permissions(self, username):
- """
- Returns a path permission checker or None if not supported
-
- :param username: session user name
- :return: an instance of BasePathPermissionChecker or None
- """
- return None
-
- def install_hooks(self, force=False):
- return self._remote.install_hooks(force)
-
- def get_hooks_info(self):
- return self._remote.get_hooks_info()
-
- def vcsserver_invalidate_cache(self, delete=False):
- return self._remote.vcsserver_invalidate_cache(delete)
-
-
-class BaseCommit(object):
+class BaseCommit:
"""
Each backend should implement it's commit representation.
@@ -933,6 +333,7 @@ class BaseCommit(object):
list of parent commits
"""
+
repository = None
branch = None
@@ -942,7 +343,7 @@ class BaseCommit(object):
value as ``None``.
"""
- _ARCHIVE_PREFIX_TEMPLATE = '{repo_name}-{short_id}'
+ _ARCHIVE_PREFIX_TEMPLATE = "{repo_name}-{short_id}"
"""
This template is used to generate a default prefix for repository archives
if no prefix has been specified.
@@ -952,7 +353,7 @@ class BaseCommit(object):
return self.__str__()
def __str__(self):
- return f'<{self.__class__.__name__} at {self.idx}:{self.short_id}>'
+ return f"<{self.__class__.__name__} at {self.idx}:{self.short_id}>"
def __eq__(self, other):
same_instance = isinstance(other, self.__class__)
@@ -962,26 +363,26 @@ class BaseCommit(object):
parents = []
try:
for parent in self.parents:
- parents.append({'raw_id': parent.raw_id})
+ parents.append({"raw_id": parent.raw_id})
except NotImplementedError:
# empty commit doesn't have parents implemented
pass
return {
- 'short_id': self.short_id,
- 'raw_id': self.raw_id,
- 'revision': self.idx,
- 'message': self.message,
- 'date': self.date,
- 'author': self.author,
- 'parents': parents,
- 'branch': self.branch
+ "short_id": self.short_id,
+ "raw_id": self.raw_id,
+ "revision": self.idx,
+ "message": self.message,
+ "date": self.date,
+ "author": self.author,
+ "parents": parents,
+ "branch": self.branch,
}
def __getstate__(self):
d = self.__dict__.copy()
- d.pop('_remote', None)
- d.pop('repository', None)
+ d.pop("_remote", None)
+ d.pop("repository", None)
return d
def get_remote(self):
@@ -992,9 +393,9 @@ class BaseCommit(object):
def _get_refs(self):
return {
- 'branches': [self.branch] if self.branch else [],
- 'bookmarks': getattr(self, 'bookmarks', []),
- 'tags': self.tags
+ "branches": [self.branch] if self.branch else [],
+ "bookmarks": getattr(self, "bookmarks", []),
+ "tags": self.tags,
}
@LazyProperty
@@ -1118,7 +519,7 @@ class BaseCommit(object):
"""
raise NotImplementedError
- def is_link(self, path):
+ def is_link(self, path: bytes):
"""
Returns ``True`` if given `path` is a symlink
"""
@@ -1154,25 +555,24 @@ class BaseCommit(object):
"""
raise NotImplementedError
- def get_path_commit(self, path, pre_load=None):
+ def get_path_commit(self, path: bytes, pre_load=None):
"""
Returns last commit of the file at the given `path`.
- :param pre_load: Optional. List of commit attributes to load.
"""
commits = self.get_path_history(path, limit=1, pre_load=pre_load)
if not commits:
raise RepositoryError(
- 'Failed to fetch history for path {}. '
- 'Please check if such path exists in your repository'.format(
- path))
+ f"Failed to fetch history for path {path}. Please check if such path exists in your repository"
+ )
return commits[0]
- def get_path_history(self, path, limit=None, pre_load=None):
+ def get_path_history(self, path: bytes, limit=None, pre_load=None):
"""
Returns history of file as reversed list of :class:`BaseCommit`
objects for which file at given `path` has been modified.
+ :param path: file path or dir path
:param limit: Optional. Allows to limit the size of the returned
history. This is intended as a hint to the underlying backend, so
that it can apply optimizations depending on the limit.
@@ -1180,16 +580,17 @@ class BaseCommit(object):
"""
raise NotImplementedError
- def get_file_annotate(self, path, pre_load=None):
+ def get_file_annotate(self, path: bytes, pre_load=None):
"""
Returns a generator of four element tuples with
lineno, sha, commit lazy loader and line
+ :param path: file path
:param pre_load: Optional. List of commit attributes to load.
"""
raise NotImplementedError
- def get_nodes(self, path, pre_load=None):
+ def get_nodes(self, path: bytes, pre_load=None):
"""
Returns combined ``DirNode`` and ``FileNode`` objects list representing
state of commit at the given ``path``.
@@ -1199,7 +600,7 @@ class BaseCommit(object):
"""
raise NotImplementedError
- def get_node(self, path):
+ def get_node(self, path: bytes, pre_load=None):
"""
Returns ``Node`` object from the given ``path``.
@@ -1208,16 +609,24 @@ class BaseCommit(object):
"""
raise NotImplementedError
- def get_largefile_node(self, path):
+ def get_largefile_node(self, path: bytes):
"""
Returns the path to largefile from Mercurial/Git-lfs storage.
or None if it's not a largefile node
"""
return None
- def archive_repo(self, archive_name_key, kind='tgz', subrepos=None,
- archive_dir_name=None, write_metadata=False, mtime=None,
- archive_at_path='/', cache_config=None):
+ def archive_repo(
+ self,
+ archive_name_key,
+ kind="tgz",
+ subrepos=None,
+ archive_dir_name=None,
+ write_metadata=False,
+ mtime=None,
+ archive_at_path="/",
+ cache_config=None,
+ ):
"""
Creates an archive containing the contents of the repository.
@@ -1237,27 +646,26 @@ class BaseCommit(object):
cache_config = cache_config or {}
allowed_kinds = [x[0] for x in settings.ARCHIVE_SPECS]
if kind not in allowed_kinds:
- raise ImproperArchiveTypeError(
- f'Archive kind ({kind}) not supported use one of {allowed_kinds}')
+ raise ImproperArchiveTypeError(f"Archive kind ({kind}) not supported use one of {allowed_kinds}")
archive_dir_name = self._validate_archive_prefix(archive_dir_name)
mtime = mtime is not None or time.mktime(self.date.timetuple())
commit_id = self.raw_id
return self.repository._remote.archive_repo(
- archive_name_key, kind, mtime, archive_at_path,
- archive_dir_name, commit_id, cache_config)
+ archive_name_key, kind, mtime, archive_at_path, archive_dir_name, commit_id, cache_config
+ )
def _validate_archive_prefix(self, archive_dir_name):
if archive_dir_name is None:
archive_dir_name = self._ARCHIVE_PREFIX_TEMPLATE.format(
- repo_name=safe_str(self.repository.name),
- short_id=self.short_id)
+ repo_name=safe_str(self.repository.name), short_id=self.short_id
+ )
elif not isinstance(archive_dir_name, str):
raise ValueError(f"archive_dir_name is not str object but: {type(archive_dir_name)}")
- elif archive_dir_name.startswith('/'):
+ elif archive_dir_name.startswith("/"):
raise VCSError("Prefix cannot start with leading slash")
- elif archive_dir_name.strip() == '':
+ elif archive_dir_name.strip() == "":
raise VCSError("Prefix cannot be empty")
elif not archive_dir_name.isascii():
raise VCSError("Prefix cannot contain non ascii characters")
@@ -1268,7 +676,7 @@ class BaseCommit(object):
"""
Returns ``RootNode`` object for this commit.
"""
- return self.get_node('')
+ return self.get_node(b"")
def next(self, branch=None):
"""
@@ -1292,8 +700,7 @@ class BaseCommit(object):
def _find_next(self, indexes, branch=None):
if branch and self.branch != branch:
- raise VCSError('Branch option used on commit not belonging '
- 'to that branch')
+ raise VCSError("Branch option used on commit not belonging " "to that branch")
for next_idx in indexes:
commit = self.repository.get_commit(commit_idx=next_idx)
@@ -1307,41 +714,17 @@ class BaseCommit(object):
Returns a `Diff` object representing the change made by this commit.
"""
parent = self.first_parent
- diff = self.repository.get_diff(
- parent, self,
- ignore_whitespace=ignore_whitespace,
- context=context)
+ diff = self.repository.get_diff(parent, self, ignore_whitespace=ignore_whitespace, context=context)
return diff
@LazyProperty
- def added(self):
- """
- Returns list of added ``FileNode`` objects.
- """
- raise NotImplementedError
-
- @LazyProperty
- def changed(self):
- """
- Returns list of modified ``FileNode`` objects.
- """
- raise NotImplementedError
-
- @LazyProperty
- def removed(self):
- """
- Returns list of removed ``FileNode`` objects.
- """
- raise NotImplementedError
-
- @LazyProperty
def size(self):
"""
Returns total number of bytes from contents of all filenodes.
"""
return sum(node.size for node in self.get_filenodes_generator())
- def walk(self, topurl=''):
+ def walk(self, top_url=b""):
"""
Similar to os.walk method. Insted of filesystem it walks through
commit starting at given ``topurl``. Returns generator of tuples
@@ -1349,10 +732,10 @@ class BaseCommit(object):
"""
from rhodecode.lib.vcs.nodes import DirNode
- if isinstance(topurl, DirNode):
- top_node = topurl
+ if isinstance(top_url, DirNode):
+ top_node = top_url
else:
- top_node = self.get_node(topurl)
+ top_node = self.get_node(top_url)
has_default_pre_load = False
if isinstance(top_node, DirNode):
@@ -1381,15 +764,20 @@ class BaseCommit(object):
def no_node_at_path(self, path):
return NodeDoesNotExistError(
- f"There is no file nor directory at the given path: "
- f"`{safe_str(path)}` at commit {self.short_id}")
+ f"There is no file nor directory at the given path: " f"`{safe_str(path)}` at commit {self.short_id}"
+ )
- def _fix_path(self, path: str) -> str:
+ @classmethod
+ def _fix_path(cls, path: bytes) -> bytes:
"""
- Paths are stored without trailing slash so we need to get rid off it if
- needed.
+ Paths are stored without trailing slash so we need to get rid off it if needed.
+ It also validates that path is a bytestring for support of mixed encodings...
"""
- return safe_str(path).rstrip('/')
+
+ if not isinstance(path, bytes):
+ raise ValueError(f'path=`{safe_str(path)}` must be bytes')
+
+ return path.rstrip(b"/")
#
# Deprecated API based on changesets
@@ -1410,17 +798,644 @@ class BaseCommit(object):
return self.get_path_commit(path)
+class BaseRepository(object):
+ """
+ Base Repository for final backends
+
+ .. attribute:: DEFAULT_BRANCH_NAME
+
+ name of default branch (i.e. "trunk" for svn, "master" for git etc.
+
+ .. attribute:: commit_ids
+
+ list of all available commit ids, in ascending order
+
+ .. attribute:: path
+
+ absolute path to the repository
+
+ .. attribute:: bookmarks
+
+ Mapping from name to :term:`Commit ID` of the bookmark. Empty in case
+ there are no bookmarks or the backend implementation does not support
+ bookmarks.
+
+ .. attribute:: tags
+
+ Mapping from name to :term:`Commit ID` of the tag.
+
+ """
+
+ DEFAULT_BRANCH_NAME = None
+ DEFAULT_CONTACT = "Unknown"
+ DEFAULT_DESCRIPTION = "unknown"
+ EMPTY_COMMIT_ID = "0" * 40
+ COMMIT_ID_PAT = re.compile(r"[0-9a-fA-F]{40}")
+
+ path = None
+
+ _is_empty = None
+ _commit_ids = {}
+
+ def __init__(self, repo_path, config=None, create=False, **kwargs):
+ """
+ Initializes repository. Raises RepositoryError if repository could
+ not be find at the given ``repo_path`` or directory at ``repo_path``
+ exists and ``create`` is set to True.
+
+ :param repo_path: local path of the repository
+ :param config: repository configuration
+ :param create=False: if set to True, would try to create repository.
+ :param src_url=None: if set, should be proper url from which repository
+ would be cloned; requires ``create`` parameter to be set to True -
+ raises RepositoryError if src_url is set and create evaluates to
+ False
+ """
+ raise NotImplementedError
+
+ def __repr__(self):
+ return f"<{self.__class__.__name__} at {self.path}>"
+
+ def __len__(self):
+ return self.count()
+
+ def __eq__(self, other):
+ same_instance = isinstance(other, self.__class__)
+ return same_instance and other.path == self.path
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ @classmethod
+ def get_create_shadow_cache_pr_path(cls, db_repo):
+ path = db_repo.cached_diffs_dir
+ if not os.path.exists(path):
+ os.makedirs(path, 0o755)
+ return path
+
+ @classmethod
+ def get_default_config(cls, default=None):
+ config = Config()
+ if default and isinstance(default, list):
+ for section, key, val in default:
+ config.set(section, key, val)
+ return config
+
+ @LazyProperty
+ def _remote(self):
+ raise NotImplementedError
+
+ def _heads(self, branch=None):
+ return []
+
+ @LazyProperty
+ def EMPTY_COMMIT(self):
+ return EmptyCommit(self.EMPTY_COMMIT_ID)
+
+ @LazyProperty
+ def alias(self):
+ for k, v in settings.BACKENDS.items():
+ if v.split(".")[-1] == str(self.__class__.__name__):
+ return k
+
+ @LazyProperty
+ def name(self):
+ return safe_str(os.path.basename(self.path))
+
+ @LazyProperty
+ def description(self):
+ raise NotImplementedError
+
+ def refs(self):
+ """
+ returns a `dict` with branches, bookmarks, tags, and closed_branches
+ for this repository
+ """
+ return dict(
+ branches=self.branches, branches_closed=self.branches_closed, tags=self.tags, bookmarks=self.bookmarks
+ )
+
+ @LazyProperty
+ def branches(self):
+ """
+ A `dict` which maps branch names to commit ids.
+ """
+ raise NotImplementedError
+
+ @LazyProperty
+ def branches_closed(self):
+ """
+ A `dict` which maps tags names to commit ids.
+ """
+ raise NotImplementedError
+
+ @LazyProperty
+ def bookmarks(self):
+ """
+ A `dict` which maps tags names to commit ids.
+ """
+ raise NotImplementedError
+
+ @LazyProperty
+ def tags(self):
+ """
+ A `dict` which maps tags names to commit ids.
+ """
+ raise NotImplementedError
+
+ @LazyProperty
+ def size(self):
+ """
+ Returns combined size in bytes for all repository files
+ """
+ tip = self.get_commit()
+ return tip.size
+
+ def size_at_commit(self, commit_id):
+ commit = self.get_commit(commit_id)
+ return commit.size
+
+ def _check_for_empty(self):
+ no_commits = len(self._commit_ids) == 0
+ if no_commits:
+ # check on remote to be sure
+ return self._remote.is_empty()
+ else:
+ return False
+
+ def is_empty(self):
+ if rhodecode.is_test:
+ return self._check_for_empty()
+
+ if self._is_empty is None:
+ # cache empty for production, but not tests
+ self._is_empty = self._check_for_empty()
+
+ return self._is_empty
+
+ @staticmethod
+ def check_url(url, config):
+ """
+ Function will check given url and try to verify if it's a valid
+ link.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def is_valid_repository(path):
+ """
+ Check if given `path` contains a valid repository of this backend
+ """
+ raise NotImplementedError
+
+ # ==========================================================================
+ # COMMITS
+ # ==========================================================================
+
+ @CachedProperty
+ def commit_ids(self):
+ raise NotImplementedError
+
+ def append_commit_id(self, commit_id):
+ if commit_id not in self.commit_ids:
+ self._rebuild_cache(self.commit_ids + [commit_id])
+
+ # clear cache
+ self._invalidate_prop_cache("commit_ids")
+ self._is_empty = False
+
+ def get_commit(
+ self,
+ commit_id=None,
+ commit_idx=None,
+ pre_load=None,
+ translate_tag=None,
+ maybe_unreachable=False,
+ reference_obj=None,
+ ) -> BaseCommit:
+ """
+ Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
+ are both None, most recent commit is returned.
+
+ :param pre_load: Optional. List of commit attributes to load.
+
+ :raises ``EmptyRepositoryError``: if there are no commits
+ """
+ raise NotImplementedError
+
+ def __iter__(self):
+ for commit_id in self.commit_ids:
+ yield self.get_commit(commit_id=commit_id)
+
+ def get_commits(
+ self,
+ start_id=None,
+ end_id=None,
+ start_date=None,
+ end_date=None,
+ branch_name=None,
+ show_hidden=False,
+ pre_load=None,
+ translate_tags=None,
+ ):
+ """
+ Returns iterator of `BaseCommit` objects from start to end
+ not inclusive. This should behave just like a list, ie. end is not
+ inclusive.
+
+ :param start_id: None or str, must be a valid commit id
+ :param end_id: None or str, must be a valid commit id
+ :param start_date:
+ :param end_date:
+ :param branch_name:
+ :param show_hidden:
+ :param pre_load:
+ :param translate_tags:
+ """
+ raise NotImplementedError
+
+ def __getitem__(self, key):
+ """
+ Allows index based access to the commit objects of this repository.
+ """
+ pre_load = ["author", "branch", "date", "message", "parents"]
+ if isinstance(key, slice):
+ return self._get_range(key, pre_load)
+ return self.get_commit(commit_idx=key, pre_load=pre_load)
+
+ def _get_range(self, slice_obj, pre_load):
+ for commit_id in self.commit_ids.__getitem__(slice_obj):
+ yield self.get_commit(commit_id=commit_id, pre_load=pre_load)
+
+ def count(self):
+ return len(self.commit_ids)
+
+ def tag(self, name, user, commit_id=None, message=None, date=None, **opts):
+ """
+ Creates and returns a tag for the given ``commit_id``.
+
+ :param name: name for new tag
+ :param user: full username, i.e.: "Joe Doe "
+ :param commit_id: commit id for which new tag would be created
+ :param message: message of the tag's commit
+ :param date: date of tag's commit
+
+ :raises TagAlreadyExistError: if tag with same name already exists
+ """
+ raise NotImplementedError
+
+ def remove_tag(self, name, user, message=None, date=None):
+ """
+ Removes tag with the given ``name``.
+
+ :param name: name of the tag to be removed
+ :param user: full username, i.e.: "Joe Doe "
+ :param message: message of the tag's removal commit
+ :param date: date of tag's removal commit
+
+ :raises TagDoesNotExistError: if tag with given name does not exists
+ """
+ raise NotImplementedError
+
+ def get_diff(self, commit1, commit2, path=None, ignore_whitespace=False, context=3, path1=None):
+ """
+ Returns (git like) *diff*, as plain text. Shows changes introduced by
+ `commit2` since `commit1`.
+
+ :param commit1: Entry point from which diff is shown. Can be
+ ``self.EMPTY_COMMIT`` - in this case, patch showing all
+ the changes since empty state of the repository until `commit2`
+ :param commit2: Until which commit changes should be shown.
+ :param path: Can be set to a path of a file to create a diff of that
+ file. If `path1` is also set, this value is only associated to
+ `commit2`.
+ :param ignore_whitespace: If set to ``True``, would not show whitespace
+ changes. Defaults to ``False``.
+ :param context: How many lines before/after changed lines should be
+ shown. Defaults to ``3``.
+ :param path1: Can be set to a path to associate with `commit1`. This
+ parameter works only for backends which support diff generation for
+ different paths. Other backends will raise a `ValueError` if `path1`
+ is set and has a different value than `path`.
+ :param file_path: filter this diff by given path pattern
+ """
+ raise NotImplementedError
+
+ def strip(self, commit_id, branch=None):
+ """
+ Strip given commit_id from the repository
+ """
+ raise NotImplementedError
+
+ def get_common_ancestor(self, commit_id1, commit_id2, repo2):
+ """
+ Return a latest common ancestor commit if one exists for this repo
+ `commit_id1` vs `commit_id2` from `repo2`.
+
+ :param commit_id1: Commit it from this repository to use as a
+ target for the comparison.
+ :param commit_id2: Source commit id to use for comparison.
+ :param repo2: Source repository to use for comparison.
+ """
+ raise NotImplementedError
+
+ def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
+ """
+ Compare this repository's revision `commit_id1` with `commit_id2`.
+
+ Returns a tuple(commits, ancestor) that would be merged from
+ `commit_id2`. Doing a normal compare (``merge=False``), ``None``
+ will be returned as ancestor.
+
+ :param commit_id1: Commit it from this repository to use as a
+ target for the comparison.
+ :param commit_id2: Source commit id to use for comparison.
+ :param repo2: Source repository to use for comparison.
+ :param merge: If set to ``True`` will do a merge compare which also
+ returns the common ancestor.
+ :param pre_load: Optional. List of commit attributes to load.
+ """
+ raise NotImplementedError
+
+ def merge(
+ self,
+ repo_id,
+ workspace_id,
+ target_ref,
+ source_repo,
+ source_ref,
+ user_name="",
+ user_email="",
+ message="",
+ dry_run=False,
+ use_rebase=False,
+ close_branch=False,
+ ):
+ """
+ Merge the revisions specified in `source_ref` from `source_repo`
+ onto the `target_ref` of this repository.
+
+ `source_ref` and `target_ref` are named tupls with the following
+ fields `type`, `name` and `commit_id`.
+
+ Returns a MergeResponse named tuple with the following fields
+ 'possible', 'executed', 'source_commit', 'target_commit',
+ 'merge_commit'.
+
+ :param repo_id: `repo_id` target repo id.
+ :param workspace_id: `workspace_id` unique identifier.
+ :param target_ref: `target_ref` points to the commit on top of which
+ the `source_ref` should be merged.
+ :param source_repo: The repository that contains the commits to be
+ merged.
+ :param source_ref: `source_ref` points to the topmost commit from
+ the `source_repo` which should be merged.
+ :param user_name: Merge commit `user_name`.
+ :param user_email: Merge commit `user_email`.
+ :param message: Merge commit `message`.
+ :param dry_run: If `True` the merge will not take place.
+ :param use_rebase: If `True` commits from the source will be rebased
+ on top of the target instead of being merged.
+ :param close_branch: If `True` branch will be close before merging it
+ """
+ if dry_run:
+ message = message or settings.MERGE_DRY_RUN_MESSAGE
+ user_email = user_email or settings.MERGE_DRY_RUN_EMAIL
+ user_name = user_name or settings.MERGE_DRY_RUN_USER
+ else:
+ if not user_name:
+ raise ValueError("user_name cannot be empty")
+ if not user_email:
+ raise ValueError("user_email cannot be empty")
+ if not message:
+ raise ValueError("message cannot be empty")
+
+ try:
+ return self._merge_repo(
+ repo_id,
+ workspace_id,
+ target_ref,
+ source_repo,
+ source_ref,
+ message,
+ user_name,
+ user_email,
+ dry_run=dry_run,
+ use_rebase=use_rebase,
+ close_branch=close_branch,
+ )
+ except RepositoryError as exc:
+ log.exception("Unexpected failure when running merge, dry-run=%s", dry_run)
+ return MergeResponse(False, False, None, MergeFailureReason.UNKNOWN, metadata={"exception": str(exc)})
+
+ def _merge_repo(
+ self,
+ repo_id,
+ workspace_id,
+ target_ref,
+ source_repo,
+ source_ref,
+ merge_message,
+ merger_name,
+ merger_email,
+ dry_run=False,
+ use_rebase=False,
+ close_branch=False,
+ ):
+ """Internal implementation of merge."""
+ raise NotImplementedError
+
+ def _maybe_prepare_merge_workspace(self, repo_id, workspace_id, target_ref, source_ref):
+ """
+ Create the merge workspace.
+
+ :param workspace_id: `workspace_id` unique identifier.
+ """
+ raise NotImplementedError
+
+ @classmethod
+ def _get_legacy_shadow_repository_path(cls, repo_path, workspace_id):
+ """
+ Legacy version that was used before. We still need it for
+ backward compat
+ """
+ return os.path.join(os.path.dirname(repo_path), f".__shadow_{os.path.basename(repo_path)}_{workspace_id}")
+
+ @classmethod
+ def _get_shadow_repository_path(cls, repo_path, repo_id, workspace_id):
+ # The name of the shadow repository must start with '.', so it is
+ # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
+ legacy_repository_path = cls._get_legacy_shadow_repository_path(repo_path, workspace_id)
+ if os.path.exists(legacy_repository_path):
+ return legacy_repository_path
+ else:
+ return os.path.join(os.path.dirname(repo_path), f".__shadow_repo_{repo_id}_{workspace_id}")
+
+ def cleanup_merge_workspace(self, repo_id, workspace_id):
+ """
+ Remove merge workspace.
+
+ This function MUST not fail in case there is no workspace associated to
+ the given `workspace_id`.
+
+ :param workspace_id: `workspace_id` unique identifier.
+ """
+ shadow_repository_path = self._get_shadow_repository_path(self.path, repo_id, workspace_id)
+ shadow_repository_path_del = "{}.{}.delete".format(shadow_repository_path, time.time())
+
+ # move the shadow repo, so it never conflicts with the one used.
+ # we use this method because shutil.rmtree had some edge case problems
+ # removing symlinked repositories
+ if not os.path.isdir(shadow_repository_path):
+ return
+
+ shutil.move(shadow_repository_path, shadow_repository_path_del)
+ try:
+ shutil.rmtree(shadow_repository_path_del, ignore_errors=False)
+ except Exception:
+ log.exception("Failed to gracefully remove shadow repo under %s", shadow_repository_path_del)
+ shutil.rmtree(shadow_repository_path_del, ignore_errors=True)
+
+ # ========== #
+ # COMMIT API #
+ # ========== #
+
+ @LazyProperty
+ def in_memory_commit(self):
+ """
+ Returns :class:`InMemoryCommit` object for this repository.
+ """
+ raise NotImplementedError
+
+ # ======================== #
+ # UTILITIES FOR SUBCLASSES #
+ # ======================== #
+
+ def _validate_diff_commits(self, commit1, commit2):
+ """
+ Validates that the given commits are related to this repository.
+
+ Intended as a utility for sub classes to have a consistent validation
+ of input parameters in methods like :meth:`get_diff`.
+ """
+ self._validate_commit(commit1)
+ self._validate_commit(commit2)
+ if isinstance(commit1, EmptyCommit) and isinstance(commit2, EmptyCommit):
+ raise ValueError("Cannot compare two empty commits")
+
+ def _validate_commit(self, commit):
+ if not isinstance(commit, BaseCommit):
+ raise TypeError("%s is not of type BaseCommit" % repr(commit))
+ if commit.repository != self and not isinstance(commit, EmptyCommit):
+ raise ValueError(
+ "Commit %s must be a valid commit from this repository %s, "
+ "related to this repository instead %s." % (commit, self, commit.repository)
+ )
+
+ def _validate_commit_id(self, commit_id):
+ if not isinstance(commit_id, str):
+ raise TypeError(f"commit_id must be a string value got {type(commit_id)} instead")
+
+ def _validate_commit_idx(self, commit_idx):
+ if not isinstance(commit_idx, int):
+ raise TypeError(f"commit_idx must be a numeric value, got {type(commit_idx)}")
+
+ def _validate_branch_name(self, branch_name):
+ if branch_name and branch_name not in self.branches_all:
+ msg = f"Branch {branch_name} not found in {self}"
+ raise BranchDoesNotExistError(msg)
+
+ #
+ # Supporting deprecated API parts
+ # TODO: johbo: consider to move this into a mixin
+ #
+
+ @property
+ def EMPTY_CHANGESET(self):
+ warnings.warn("Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning)
+ return self.EMPTY_COMMIT_ID
+
+ @property
+ def revisions(self):
+ warnings.warn("Use commits attribute instead", DeprecationWarning)
+ return self.commit_ids
+
+ @revisions.setter
+ def revisions(self, value):
+ warnings.warn("Use commits attribute instead", DeprecationWarning)
+ self.commit_ids = value
+
+ def get_changeset(self, revision=None, pre_load=None):
+ warnings.warn("Use get_commit instead", DeprecationWarning)
+ commit_id = None
+ commit_idx = None
+ if isinstance(revision, str):
+ commit_id = revision
+ else:
+ commit_idx = revision
+ return self.get_commit(commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
+
+ def get_changesets(self, start=None, end=None, start_date=None, end_date=None, branch_name=None, pre_load=None):
+ warnings.warn("Use get_commits instead", DeprecationWarning)
+ start_id = self._revision_to_commit(start)
+ end_id = self._revision_to_commit(end)
+ return self.get_commits(
+ start_id=start_id,
+ end_id=end_id,
+ start_date=start_date,
+ end_date=end_date,
+ branch_name=branch_name,
+ pre_load=pre_load,
+ )
+
+ def _revision_to_commit(self, revision):
+ """
+ Translates a revision to a commit_id
+
+ Helps to support the old changeset based API which allows to use
+ commit ids and commit indices interchangeable.
+ """
+ if revision is None:
+ return revision
+
+ if isinstance(revision, str):
+ commit_id = revision
+ else:
+ commit_id = self.commit_ids[revision]
+ return commit_id
+
+ @property
+ def in_memory_changeset(self):
+ warnings.warn("Use in_memory_commit instead", DeprecationWarning)
+ return self.in_memory_commit
+
+ def get_path_permissions(self, username):
+ """
+ Returns a path permission checker or None if not supported
+
+ :param username: session user name
+ :return: an instance of BasePathPermissionChecker or None
+ """
+ return None
+
+ def install_hooks(self, force=False):
+ return self._remote.install_hooks(force)
+
+ def get_hooks_info(self):
+ return self._remote.get_hooks_info()
+
+ def vcsserver_invalidate_cache(self, delete=False):
+ return self._remote.vcsserver_invalidate_cache(delete)
+
+
class BaseChangesetClass(type):
-
def __instancecheck__(self, instance):
return isinstance(instance, BaseCommit)
class BaseChangeset(BaseCommit, metaclass=BaseChangesetClass):
-
def __new__(cls, *args, **kwargs):
- warnings.warn(
- "Use BaseCommit instead of BaseChangeset", DeprecationWarning)
+ warnings.warn("Use BaseCommit instead of BaseChangeset", DeprecationWarning)
return super().__new__(cls, *args, **kwargs)
@@ -1441,8 +1456,7 @@ class BaseInMemoryCommit(object):
list of ``FileNode`` objects marked as *changed*
``removed``
- list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
- *removed*
+ list of ``FileNode`` objects marked to be *removed*
``parents``
list of :class:`BaseCommit` instances representing parents of
@@ -1469,9 +1483,7 @@ class BaseInMemoryCommit(object):
# Check if not already marked as *added* first
for node in filenodes:
if node.path in (n.path for n in self.added):
- raise NodeAlreadyAddedError(
- "Such FileNode %s is already marked for addition"
- % node.path)
+ raise NodeAlreadyAddedError(f"Such FileNode {node.path} is already marked for addition")
for node in filenodes:
self.added.append(node)
@@ -1490,24 +1502,19 @@ class BaseInMemoryCommit(object):
"""
for node in filenodes:
if node.path in (n.path for n in self.removed):
- raise NodeAlreadyRemovedError(
- "Node at %s is already marked as removed" % node.path)
+ raise NodeAlreadyRemovedError("Node at %s is already marked as removed" % node.path)
try:
self.repository.get_commit()
except EmptyRepositoryError:
- raise EmptyRepositoryError(
- "Nothing to change - try to *add* new nodes rather than "
- "changing them")
+ raise EmptyRepositoryError("Nothing to change - try to *add* new nodes rather than " "changing them")
for node in filenodes:
if node.path in (n.path for n in self.changed):
- raise NodeAlreadyChangedError(
- "Node at '%s' is already marked as changed" % node.path)
+ raise NodeAlreadyChangedError("Node at '%s' is already marked as changed" % node.path)
self.changed.append(node)
def remove(self, *filenodes):
"""
- Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
- *removed* in next commit.
+ Marks given ``FileNode`` objects to be *removed* in next commit.
:raises ``NodeAlreadyRemovedError``: if node has been already marked to
be *removed*
@@ -1516,11 +1523,9 @@ class BaseInMemoryCommit(object):
"""
for node in filenodes:
if node.path in (n.path for n in self.removed):
- raise NodeAlreadyRemovedError(
- "Node is already marked to for removal at %s" % node.path)
+ raise NodeAlreadyRemovedError("Node is already marked to for removal at %s" % node.path)
if node.path in (n.path for n in self.changed):
- raise NodeAlreadyChangedError(
- "Node is already marked to be changed at %s" % node.path)
+ raise NodeAlreadyChangedError("Node is already marked to be changed at %s" % node.path)
# We only mark node as *removed* - real removal is done by
# commit method
self.removed.append(node)
@@ -1575,12 +1580,11 @@ class BaseInMemoryCommit(object):
for p in parents:
for node in self.added:
try:
- p.get_node(node.path)
+ p.get_node(node.bytes_path)
except NodeDoesNotExistError:
pass
else:
- raise NodeAlreadyExistsError(
- f"Node `{node.path}` already exists at {p}")
+ raise NodeAlreadyExistsError(f"Node `{node.path}` already exists at {p}")
# Check nodes marked as changed
missing = set(self.changed)
@@ -1590,7 +1594,7 @@ class BaseInMemoryCommit(object):
for p in parents:
for node in self.changed:
try:
- old = p.get_node(node.path)
+ old = p.get_node(node.bytes_path)
missing.remove(node)
# if content actually changed, remove node from not_changed
if old.content != node.content:
@@ -1598,24 +1602,23 @@ class BaseInMemoryCommit(object):
except NodeDoesNotExistError:
pass
if self.changed and missing:
- raise NodeDoesNotExistError(
- f"Node `{node.path}` marked as modified but missing in parents: {parents}")
+ raise NodeDoesNotExistError(f"Node `{node.path}` marked as modified but missing in parents: {parents}")
if self.changed and not_changed:
raise NodeNotChangedError(
- "Node `%s` wasn't actually changed (parents: %s)"
- % (not_changed.pop().path, parents))
+ "Node `%s` wasn't actually changed (parents: %s)" % (not_changed.pop().path, parents)
+ )
# Check nodes marked as removed
if self.removed and not parents:
raise NodeDoesNotExistError(
- "Cannot remove node at %s as there "
- "were no parents specified" % self.removed[0].path)
+ "Cannot remove node at %s as there " "were no parents specified" % self.removed[0].path
+ )
really_removed = set()
for p in parents:
for node in self.removed:
try:
- p.get_node(node.path)
+ p.get_node(node.bytes_path)
really_removed.add(node)
except CommitError:
pass
@@ -1623,8 +1626,8 @@ class BaseInMemoryCommit(object):
if not_removed:
# TODO: johbo: This code branch does not seem to be covered
raise NodeDoesNotExistError(
- "Cannot remove node at %s from "
- "following parents: %s" % (not_removed, parents))
+ "Cannot remove node at %s from " "following parents: %s" % (not_removed, parents)
+ )
def commit(self, message, author, parents=None, branch=None, date=None, **kwargs):
"""
@@ -1652,16 +1655,13 @@ class BaseInMemoryCommit(object):
class BaseInMemoryChangesetClass(type):
-
def __instancecheck__(self, instance):
return isinstance(instance, BaseInMemoryCommit)
class BaseInMemoryChangeset(BaseInMemoryCommit, metaclass=BaseInMemoryChangesetClass):
-
def __new__(cls, *args, **kwargs):
- warnings.warn(
- "Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning)
+ warnings.warn("Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning)
return super().__new__(cls, *args, **kwargs)
@@ -1671,9 +1671,7 @@ class EmptyCommit(BaseCommit):
an EmptyCommit
"""
- def __init__(
- self, commit_id=EMPTY_COMMIT_ID, repo=None, alias=None, idx=-1,
- message='', author='', date=None):
+ def __init__(self, commit_id=EMPTY_COMMIT_ID, repo=None, alias=None, idx=-1, message="", author="", date=None):
self._empty_commit_id = commit_id
# TODO: johbo: Solve idx parameter, default value does not make
# too much sense
@@ -1697,6 +1695,7 @@ class EmptyCommit(BaseCommit):
def branch(self):
if self.alias:
from rhodecode.lib.vcs.backends import get_backend
+
return get_backend(self.alias).DEFAULT_BRANCH_NAME
@LazyProperty
@@ -1711,7 +1710,7 @@ class EmptyCommit(BaseCommit):
return self
def get_file_content(self, path) -> bytes:
- return b''
+ return b""
def get_file_content_streamed(self, path):
yield self.get_file_content(path)
@@ -1721,27 +1720,29 @@ class EmptyCommit(BaseCommit):
class EmptyChangesetClass(type):
-
def __instancecheck__(self, instance):
return isinstance(instance, EmptyCommit)
class EmptyChangeset(EmptyCommit, metaclass=EmptyChangesetClass):
-
def __new__(cls, *args, **kwargs):
- warnings.warn(
- "Use EmptyCommit instead of EmptyChangeset", DeprecationWarning)
+ warnings.warn("Use EmptyCommit instead of EmptyChangeset", DeprecationWarning)
return super(EmptyCommit, cls).__new__(cls, *args, **kwargs)
- def __init__(self, cs=EMPTY_COMMIT_ID, repo=None, requested_revision=None,
- alias=None, revision=-1, message='', author='', date=None):
+ def __init__(
+ self,
+ cs=EMPTY_COMMIT_ID,
+ repo=None,
+ requested_revision=None,
+ alias=None,
+ revision=-1,
+ message="",
+ author="",
+ date=None,
+ ):
if requested_revision is not None:
- warnings.warn(
- "Parameter requested_revision not supported anymore",
- DeprecationWarning)
- super().__init__(
- commit_id=cs, repo=repo, alias=alias, idx=revision,
- message=message, author=author, date=date)
+ warnings.warn("Parameter requested_revision not supported anymore", DeprecationWarning)
+ super().__init__(commit_id=cs, repo=repo, alias=alias, idx=revision, message=message, author=author, date=date)
@property
def revision(self):
@@ -1760,11 +1761,11 @@ class EmptyRepository(BaseRepository):
def get_diff(self, *args, **kwargs):
from rhodecode.lib.vcs.backends.git.diff import GitDiff
- return GitDiff(b'')
+
+ return GitDiff(b"")
class CollectionGenerator(object):
-
def __init__(self, repo, commit_ids, collection_size=None, pre_load=None, translate_tag=None):
self.repo = repo
self.commit_ids = commit_ids
@@ -1786,26 +1787,22 @@ class CollectionGenerator(object):
"""
Allows backends to override the way commits are generated.
"""
- return self.repo.get_commit(
- commit_id=commit_id, pre_load=self.pre_load,
- translate_tag=self.translate_tag)
+ return self.repo.get_commit(commit_id=commit_id, pre_load=self.pre_load, translate_tag=self.translate_tag)
def __getitem__(self, key):
"""Return either a single element by index, or a sliced collection."""
if isinstance(key, slice):
- commit_ids = self.commit_ids[key.start:key.stop]
+ commit_ids = self.commit_ids[key.start : key.stop]
else:
# single item
commit_ids = self.commit_ids[key]
- return self.__class__(
- self.repo, commit_ids, pre_load=self.pre_load,
- translate_tag=self.translate_tag)
+ return self.__class__(self.repo, commit_ids, pre_load=self.pre_load, translate_tag=self.translate_tag)
def __repr__(self):
- return '' % (self.__len__())
+ return "" % (self.__len__())
class Config(object):
@@ -1826,8 +1823,7 @@ class Config(object):
return clone
def __repr__(self):
- return ''.format(
- len(self._values), hex(id(self)))
+ return "".format(len(self._values), hex(id(self)))
def items(self, section):
return self._values.get(section, {}).items()
@@ -1844,7 +1840,7 @@ class Config(object):
def drop_option(self, section, option):
if section not in self._values:
- raise ValueError(f'Section {section} does not exist')
+ raise ValueError(f"Section {section} does not exist")
del self._values[section][option]
def serialize(self):
@@ -1855,8 +1851,7 @@ class Config(object):
items = []
for section in self._values:
for option, value in self._values[section].items():
- items.append(
- (safe_str(section), safe_str(option), safe_str(value)))
+ items.append((safe_str(section), safe_str(option), safe_str(value)))
return items
@@ -1867,12 +1862,13 @@ class Diff(object):
Subclasses have to provide a backend specific value for
:attr:`_header_re` and :attr:`_meta_re`.
"""
+
_meta_re = None
- _header_re: bytes = re.compile(br"")
+ _header_re: bytes = re.compile(rb"")
def __init__(self, raw_diff: bytes):
if not isinstance(raw_diff, bytes):
- raise Exception(f'raw_diff must be bytes - got {type(raw_diff)}')
+ raise Exception(f"raw_diff must be bytes - got {type(raw_diff)}")
self.raw = memoryview(raw_diff)
@@ -1886,7 +1882,7 @@ class Diff(object):
we can detect last chunk as this was also has special rule
"""
- diff_parts = (b'\n' + bytes(self.raw)).split(b'\ndiff --git')
+ diff_parts = (b"\n" + bytes(self.raw)).split(b"\ndiff --git")
chunks = diff_parts[1:]
total_chunks = len(chunks)
@@ -1894,44 +1890,46 @@ class Diff(object):
def diff_iter(_chunks):
for cur_chunk, chunk in enumerate(_chunks, start=1):
yield DiffChunk(chunk, self, cur_chunk == total_chunks)
+
return diff_iter(chunks)
class DiffChunk(object):
-
def __init__(self, chunk: bytes, diff_obj: Diff, is_last_chunk: bool):
self.diff_obj = diff_obj
# since we split by \ndiff --git that part is lost from original diff
# we need to re-apply it at the end, EXCEPT ! if it's last chunk
if not is_last_chunk:
- chunk += b'\n'
+ chunk += b"\n"
header_re = self.diff_obj.get_header_re()
+
match = header_re.match(chunk)
self.header = match.groupdict()
- self.diff = chunk[match.end():]
+ self.diff = chunk[match.end() :]
self.raw = chunk
@property
def header_as_str(self):
if self.header:
+
def safe_str_on_bytes(val):
if isinstance(val, bytes):
return safe_str(val)
return val
+
return {safe_str(k): safe_str_on_bytes(v) for k, v in self.header.items()}
def __repr__(self):
- return f'DiffChunk({self.header_as_str})'
+ return f"DiffChunk({self.header_as_str})"
class BasePathPermissionChecker(object):
-
@staticmethod
def create_from_patterns(includes, excludes):
- if includes and '*' in includes and not excludes:
+ if includes and "*" in includes and not excludes:
return AllPathPermissionChecker()
- elif excludes and '*' in excludes:
+ elif excludes and "*" in excludes:
return NonePathPermissionChecker()
else:
return PatternPathPermissionChecker(includes, excludes)
@@ -1945,7 +1943,6 @@ class BasePathPermissionChecker(object):
class AllPathPermissionChecker(BasePathPermissionChecker):
-
@property
def has_full_access(self):
return True
@@ -1955,7 +1952,6 @@ class AllPathPermissionChecker(BasePathP
class NonePathPermissionChecker(BasePathPermissionChecker):
-
@property
def has_full_access(self):
return False
@@ -1965,18 +1961,15 @@ class NonePathPermissionChecker(BasePath
class PatternPathPermissionChecker(BasePathPermissionChecker):
-
def __init__(self, includes, excludes):
self.includes = includes
self.excludes = excludes
- self.includes_re = [] if not includes else [
- re.compile(fnmatch.translate(pattern)) for pattern in includes]
- self.excludes_re = [] if not excludes else [
- re.compile(fnmatch.translate(pattern)) for pattern in excludes]
+ self.includes_re = [] if not includes else [re.compile(fnmatch.translate(pattern)) for pattern in includes]
+ self.excludes_re = [] if not excludes else [re.compile(fnmatch.translate(pattern)) for pattern in excludes]
@property
def has_full_access(self):
- return '*' in self.includes and not self.excludes
+ return "*" in self.includes and not self.excludes
def has_access(self, path):
for regex in self.excludes_re:
diff --git a/rhodecode/lib/vcs/backends/git/__init__.py b/rhodecode/lib/vcs/backends/git/__init__.py
--- a/rhodecode/lib/vcs/backends/git/__init__.py
+++ b/rhodecode/lib/vcs/backends/git/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -45,10 +45,3 @@ def discover_git_version(raise_on_exc=Fa
if raise_on_exc:
raise
return ''
-
-
-def lfs_store(base_location):
- """
- Return a lfs store relative to base_location
- """
- return os.path.join(base_location, '.cache', 'lfs_store')
diff --git a/rhodecode/lib/vcs/backends/git/commit.py b/rhodecode/lib/vcs/backends/git/commit.py
--- a/rhodecode/lib/vcs/backends/git/commit.py
+++ b/rhodecode/lib/vcs/backends/git/commit.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,8 +21,8 @@ GIT commit module
"""
import io
-import stat
import configparser
+import logging
from itertools import chain
from zope.cachedescriptors.property import Lazy as LazyProperty
@@ -32,9 +32,16 @@ from rhodecode.lib.str_utils import safe
from rhodecode.lib.vcs.backends import base
from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
from rhodecode.lib.vcs.nodes import (
- FileNode, DirNode, NodeKind, RootNode, SubModuleNode,
- ChangedFileNodesGenerator, AddedFileNodesGenerator,
- RemovedFileNodesGenerator, LargeFileNode)
+ FileNode,
+ DirNode,
+ NodeKind,
+ RootNode,
+ SubModuleNode,
+ LargeFileNode,
+)
+from rhodecode.lib.vcs_common import FILEMODE_LINK
+
+log = logging.getLogger(__name__)
class GitCommit(base.BaseCommit):
@@ -50,13 +57,11 @@ class GitCommit(base.BaseCommit):
# done through a more complex tree walk on parents
"status",
# mercurial specific property not supported here
- "_file_paths",
- # mercurial specific property not supported here
- 'obsolete',
+ "obsolete",
# mercurial specific property not supported here
- 'phase',
+ "phase",
# mercurial specific property not supported here
- 'hidden'
+ "hidden",
]
def __init__(self, repository, raw_id, idx, pre_load=None):
@@ -69,17 +74,16 @@ class GitCommit(base.BaseCommit):
self._set_bulk_properties(pre_load)
# caches
- self._stat_modes = {} # stat info for paths
- self._paths = {} # path processed with parse_tree
self.nodes = {}
+ self._path_mode_cache = {} # path stats cache, e.g filemode etc
+ self._path_type_cache = {} # path type dir/file/link etc cache
+
self._submodules = None
def _set_bulk_properties(self, pre_load):
-
if not pre_load:
return
- pre_load = [entry for entry in pre_load
- if entry not in self._filter_pre_load]
+ pre_load = [entry for entry in pre_load if entry not in self._filter_pre_load]
if not pre_load:
return
@@ -102,7 +106,7 @@ class GitCommit(base.BaseCommit):
@LazyProperty
def _tree_id(self):
- return self._remote[self._commit['tree']]['id']
+ return self._remote[self._commit["tree"]]["id"]
@LazyProperty
def id(self):
@@ -134,13 +138,12 @@ class GitCommit(base.BaseCommit):
"""
Returns modified, added, removed, deleted files for current commit
"""
- return self.changed, self.added, self.removed
+ added, modified, deleted = self._changes_cache
+ return list(modified), list(modified), list(deleted)
@LazyProperty
def tags(self):
- tags = [safe_str(name) for name,
- commit_id in self.repository.tags.items()
- if commit_id == self.raw_id]
+ tags = [safe_str(name) for name, commit_id in self.repository.tags.items() if commit_id == self.raw_id]
return tags
@LazyProperty
@@ -161,47 +164,33 @@ class GitCommit(base.BaseCommit):
branches = self._remote.branch(self.raw_id)
return self._set_branch(branches)
- def _get_tree_id_for_path(self, path):
+ def _get_path_tree_id_and_type(self, path: bytes):
- path = safe_str(path)
- if path in self._paths:
- return self._paths[path]
-
- tree_id = self._tree_id
+ if path in self._path_type_cache:
+ return self._path_type_cache[path]
- path = path.strip('/')
- if path == '':
- data = [tree_id, "tree"]
- self._paths[''] = data
- return data
+ if path == b"":
+ self._path_type_cache[b""] = [self._tree_id, NodeKind.DIR]
+ return self._path_type_cache[path]
- tree_id, tree_type, tree_mode = \
- self._remote.tree_and_type_for_path(self.raw_id, path)
+ tree_id, tree_type, tree_mode = self._remote.tree_and_type_for_path(self.raw_id, path)
if tree_id is None:
raise self.no_node_at_path(path)
- self._paths[path] = [tree_id, tree_type]
- self._stat_modes[path] = tree_mode
+ self._path_type_cache[path] = [tree_id, tree_type]
+ self._path_mode_cache[path] = tree_mode
- if path not in self._paths:
- raise self.no_node_at_path(path)
-
- return self._paths[path]
+ return self._path_type_cache[path]
def _get_kind(self, path):
- tree_id, type_ = self._get_tree_id_for_path(path)
- if type_ == 'blob':
- return NodeKind.FILE
- elif type_ == 'tree':
- return NodeKind.DIR
- elif type_ == 'link':
- return NodeKind.SUBMODULE
- return None
+ path = self._fix_path(path)
+ _, path_type = self._get_path_tree_id_and_type(path)
+ return path_type
def _assert_is_path(self, path):
path = self._fix_path(path)
if self._get_kind(path) != NodeKind.FILE:
- raise CommitError(f"File does not exist for commit {self.raw_id} at '{path}'")
+ raise CommitError(f"File at path={path} does not exist for commit {self.raw_id}")
return path
def _get_file_nodes(self):
@@ -237,15 +226,19 @@ class GitCommit(base.BaseCommit):
path = self._assert_is_path(path)
# ensure path is traversed
- self._get_tree_id_for_path(path)
+ self._get_path_tree_id_and_type(path)
+
+ return self._path_mode_cache[path]
- return self._stat_modes[path]
+ def is_link(self, path: bytes):
+ path = self._assert_is_path(path)
+ if path not in self._path_mode_cache:
+ self._path_mode_cache[path] = self._remote.fctx_flags(self.raw_id, path)
- def is_link(self, path):
- return stat.S_ISLNK(self.get_file_mode(path))
+ return self._path_mode_cache[path] == FILEMODE_LINK
def is_node_binary(self, path):
- tree_id, _ = self._get_tree_id_for_path(path)
+ tree_id, _ = self._get_path_tree_id_and_type(path)
return self._remote.is_binary(tree_id)
def node_md5_hash(self, path):
@@ -256,19 +249,19 @@ class GitCommit(base.BaseCommit):
"""
Returns content of the file at given `path`.
"""
- tree_id, _ = self._get_tree_id_for_path(path)
+ tree_id, _ = self._get_path_tree_id_and_type(path)
return self._remote.blob_as_pretty_string(tree_id)
def get_file_content_streamed(self, path):
- tree_id, _ = self._get_tree_id_for_path(path)
- stream_method = getattr(self._remote, 'stream:blob_as_pretty_string')
+ tree_id, _ = self._get_path_tree_id_and_type(path)
+ stream_method = getattr(self._remote, "stream:blob_as_pretty_string")
return stream_method(tree_id)
def get_file_size(self, path):
"""
Returns size of the file at given `path`.
"""
- tree_id, _ = self._get_tree_id_for_path(path)
+ tree_id, _ = self._get_path_tree_id_and_type(path)
return self._remote.blob_raw_length(tree_id)
def get_path_history(self, path, limit=None, pre_load=None):
@@ -276,12 +269,9 @@ class GitCommit(base.BaseCommit):
Returns history of file as reversed list of `GitCommit` objects for
which file at given `path` has been modified.
"""
-
path = self._assert_is_path(path)
- hist = self._remote.node_history(self.raw_id, path, limit)
- return [
- self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
- for commit_id in hist]
+ history = self._remote.node_history(self.raw_id, path, limit)
+ return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in history]
def get_file_annotate(self, path, pre_load=None):
"""
@@ -293,95 +283,102 @@ class GitCommit(base.BaseCommit):
for ln_no, commit_id, content in result:
yield (
- ln_no, commit_id,
+ ln_no,
+ commit_id,
lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
- content)
+ content,
+ )
- def get_nodes(self, path, pre_load=None):
+ def get_nodes(self, path: bytes, pre_load=None):
if self._get_kind(path) != NodeKind.DIR:
- raise CommitError(
- f"Directory does not exist for commit {self.raw_id} at '{path}'")
+ raise CommitError(f"Directory does not exist for commit {self.raw_id} at '{path}'")
path = self._fix_path(path)
- tree_id, _ = self._get_tree_id_for_path(path)
+ path_nodes = []
- dirnodes = []
- filenodes = []
+ for obj_name, stat_, tree_item_id, node_kind, pre_load_data in self._remote.get_nodes(self.raw_id, path, pre_load):
+ if node_kind is None:
+ raise CommitError(f"Requested object type={node_kind} cannot be determined")
- # extracted tree ID gives us our files...
- str_path = safe_str(path) # libgit operates on bytes
- for name, stat_, id_, type_ in self._remote.tree_items(tree_id):
- if type_ == 'link':
- url = self._get_submodule_url('/'.join((str_path, name)))
- dirnodes.append(SubModuleNode(
- name, url=url, commit=id_, alias=self.repository.alias))
- continue
+ if path == b"":
+ obj_path = obj_name
+ else:
+ obj_path = b"/".join((path, obj_name))
+
+ # cache file mode for git, since we have it already
+ if obj_path not in self._path_mode_cache:
+ self._path_mode_cache[obj_path] = stat_
- if str_path != '':
- obj_path = '/'.join((str_path, name))
- else:
- obj_path = name
- if obj_path not in self._stat_modes:
- self._stat_modes[obj_path] = stat_
+ # cache type
+ if node_kind not in self._path_type_cache:
+ self._path_type_cache[obj_path] = [tree_item_id, node_kind]
- if type_ == 'tree':
- dirnodes.append(DirNode(safe_bytes(obj_path), commit=self))
- elif type_ == 'blob':
- filenodes.append(FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load))
+ entry = None
+ if obj_path in self.nodes:
+ entry = self.nodes[obj_path]
else:
- raise CommitError(f"Requested object should be Tree or Blob, is {type_}")
+ if node_kind == NodeKind.SUBMODULE:
+ url = self._get_submodule_url(obj_path)
+ entry= SubModuleNode(obj_name, url=url, commit=tree_item_id, alias=self.repository.alias)
+ elif node_kind == NodeKind.DIR:
+ entry = DirNode(safe_bytes(obj_path), commit=self)
+ elif node_kind == NodeKind.FILE:
+ entry = FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load_data=pre_load_data)
- nodes = dirnodes + filenodes
- for node in nodes:
- if node.path not in self.nodes:
- self.nodes[node.path] = node
- nodes.sort()
- return nodes
+ if entry:
+ self.nodes[obj_path] = entry
+ path_nodes.append(entry)
- def get_node(self, path, pre_load=None):
+ path_nodes.sort()
+ return path_nodes
+
+ def get_node(self, path: bytes, pre_load=None):
path = self._fix_path(path)
- if path not in self.nodes:
- try:
- tree_id, type_ = self._get_tree_id_for_path(path)
- except CommitError:
- raise NodeDoesNotExistError(
- f"Cannot find one of parents' directories for a given "
- f"path: {path}")
+
+ # use cached, if we have one
+ if path in self.nodes:
+ return self.nodes[path]
- if type_ in ['link', 'commit']:
+ try:
+ tree_id, path_type = self._get_path_tree_id_and_type(path)
+ except CommitError:
+ raise NodeDoesNotExistError(f"Cannot find one of parents' directories for a given path: {path}")
+
+ if path == b"":
+ node = RootNode(commit=self)
+ else:
+ if path_type == NodeKind.SUBMODULE:
url = self._get_submodule_url(path)
- node = SubModuleNode(path, url=url, commit=tree_id,
- alias=self.repository.alias)
- elif type_ == 'tree':
- if path == '':
- node = RootNode(commit=self)
- else:
- node = DirNode(safe_bytes(path), commit=self)
- elif type_ == 'blob':
+ node = SubModuleNode(path, url=url, commit=tree_id, alias=self.repository.alias)
+ elif path_type == NodeKind.DIR:
+ node = DirNode(safe_bytes(path), commit=self)
+ elif path_type == NodeKind.FILE:
node = FileNode(safe_bytes(path), commit=self, pre_load=pre_load)
- self._stat_modes[path] = node.mode
+ self._path_mode_cache[path] = node.mode
else:
raise self.no_node_at_path(path)
- # cache node
- self.nodes[path] = node
-
+ # cache node
+ self.nodes[path] = node
return self.nodes[path]
- def get_largefile_node(self, path):
- tree_id, _ = self._get_tree_id_for_path(path)
+ def get_largefile_node(self, path: bytes):
+ tree_id, _ = self._get_path_tree_id_and_type(path)
pointer_spec = self._remote.is_large_file(tree_id)
if pointer_spec:
# content of that file regular FileNode is the hash of largefile
- file_id = pointer_spec.get('oid_hash')
- if self._remote.in_largefiles_store(file_id):
- lf_path = self._remote.store_path(file_id)
- return LargeFileNode(safe_bytes(lf_path), commit=self, org_path=path)
+ file_id = pointer_spec.get("oid_hash")
+ if not self._remote.in_largefiles_store(file_id):
+ log.warning(f'Largefile oid={file_id} not found in store')
+ return None
+
+ lf_path = self._remote.store_path(file_id)
+ return LargeFileNode(safe_bytes(lf_path), commit=self, org_path=path)
@LazyProperty
- def affected_files(self):
+ def affected_files(self) -> list[bytes]:
"""
Gets a fast accessible file changes for given commit
"""
@@ -389,7 +386,7 @@ class GitCommit(base.BaseCommit):
return list(added.union(modified).union(deleted))
@LazyProperty
- def _changes_cache(self):
+ def _changes_cache(self) -> tuple[set, set, set]:
added = set()
modified = set()
deleted = set()
@@ -416,53 +413,22 @@ class GitCommit(base.BaseCommit):
:param status: one of: *added*, *modified* or *deleted*
"""
added, modified, deleted = self._changes_cache
- return sorted({
- 'added': list(added),
- 'modified': list(modified),
- 'deleted': list(deleted)}[status]
- )
-
- @LazyProperty
- def added(self):
- """
- Returns list of added ``FileNode`` objects.
- """
- if not self.parents:
- return list(self._get_file_nodes())
- return AddedFileNodesGenerator(self.added_paths, self)
+ return sorted({"added": list(added), "modified": list(modified), "deleted": list(deleted)}[status])
@LazyProperty
def added_paths(self):
- return [n for n in self._get_paths_for_status('added')]
-
- @LazyProperty
- def changed(self):
- """
- Returns list of modified ``FileNode`` objects.
- """
- if not self.parents:
- return []
- return ChangedFileNodesGenerator(self.changed_paths, self)
+ return [n for n in self._get_paths_for_status("added")]
@LazyProperty
def changed_paths(self):
- return [n for n in self._get_paths_for_status('modified')]
-
- @LazyProperty
- def removed(self):
- """
- Returns list of removed ``FileNode`` objects.
- """
- if not self.parents:
- return []
- return RemovedFileNodesGenerator(self.removed_paths, self)
+ return [n for n in self._get_paths_for_status("modified")]
@LazyProperty
def removed_paths(self):
- return [n for n in self._get_paths_for_status('deleted')]
+ return [n for n in self._get_paths_for_status("deleted")]
- def _get_submodule_url(self, submodule_path):
- git_modules_path = '.gitmodules'
+ def _get_submodule_url(self, submodule_path: bytes):
+ git_modules_path = b".gitmodules"
if self._submodules is None:
self._submodules = {}
@@ -476,9 +442,9 @@ class GitCommit(base.BaseCommit):
parser.read_file(io.StringIO(submodules_node.str_content))
for section in parser.sections():
- path = parser.get(section, 'path')
- url = parser.get(section, 'url')
+ path = parser.get(section, "path")
+ url = parser.get(section, "url")
if path and url:
- self._submodules[path.strip('/')] = url
+ self._submodules[safe_bytes(path).strip(b"/")] = url
- return self._submodules.get(submodule_path.strip('/'))
+ return self._submodules.get(submodule_path.strip(b"/"))
diff --git a/rhodecode/lib/vcs/backends/git/diff.py b/rhodecode/lib/vcs/backends/git/diff.py
--- a/rhodecode/lib/vcs/backends/git/diff.py
+++ b/rhodecode/lib/vcs/backends/git/diff.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/backends/git/inmemory.py b/rhodecode/lib/vcs/backends/git/inmemory.py
--- a/rhodecode/lib/vcs/backends/git/inmemory.py
+++ b/rhodecode/lib/vcs/backends/git/inmemory.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/backends/git/repository.py b/rhodecode/lib/vcs/backends/git/repository.py
--- a/rhodecode/lib/vcs/backends/git/repository.py
+++ b/rhodecode/lib/vcs/backends/git/repository.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -425,7 +425,7 @@ class GitRepository(BaseRepository):
return
def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
- translate_tag=True, maybe_unreachable=False, reference_obj=None):
+ translate_tag=True, maybe_unreachable=False, reference_obj=None) -> GitCommit:
"""
Returns `GitCommit` object representing commit from git repository
at the given `commit_id` or head (most recent commit) if None given.
@@ -608,8 +608,7 @@ class GitRepository(BaseRepository):
return commit_id1
if self != repo2:
- commits = self._remote.get_missing_revs(
- commit_id1, commit_id2, repo2.path)
+ commits = self._remote.get_missing_revs(commit_id1, commit_id2, repo2.path)
if commits:
commit = repo2.get_commit(commits[-1])
if commit.parents:
@@ -620,9 +619,7 @@ class GitRepository(BaseRepository):
# no commits from other repo, ancestor_id is the commit_id2
ancestor_id = commit_id2
else:
- output, __ = self.run_git_command(
- ['merge-base', commit_id1, commit_id2])
- ancestor_id = self.COMMIT_ID_PAT.findall(output)[0]
+ ancestor_id = self._remote.get_common_ancestor(commit_id1, commit_id2)
log.debug('Found common ancestor with sha: %s', ancestor_id)
@@ -630,23 +627,22 @@ class GitRepository(BaseRepository):
def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
repo1 = self
- ancestor_id = None
if commit_id1 == commit_id2:
commits = []
elif repo1 != repo2:
- missing_ids = self._remote.get_missing_revs(commit_id1, commit_id2,
- repo2.path)
+ missing_ids = self._remote.get_missing_revs(commit_id1, commit_id2, repo2.path)
commits = [
repo2.get_commit(commit_id=commit_id, pre_load=pre_load)
for commit_id in reversed(missing_ids)]
else:
- output, __ = repo1.run_git_command(
- ['log', '--reverse', '--pretty=format: %H', '-s',
- f'{commit_id1}..{commit_id2}'])
+ compare_commits = self._remote.compare_commits(
+ commit_id1, commit_id2
+ )
commits = [
repo1.get_commit(commit_id=commit_id, pre_load=pre_load)
- for commit_id in self.COMMIT_ID_PAT.findall(output)]
+ for commit_id in compare_commits]
+
return commits
diff --git a/rhodecode/lib/vcs/backends/hg/__init__.py b/rhodecode/lib/vcs/backends/hg/__init__.py
--- a/rhodecode/lib/vcs/backends/hg/__init__.py
+++ b/rhodecode/lib/vcs/backends/hg/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -45,10 +45,3 @@ def discover_hg_version(raise_on_exc=Fal
if raise_on_exc:
raise
return ''
-
-
-def largefiles_store(base_location):
- """
- Return a largefile store relative to base_location
- """
- return os.path.join(base_location, '.cache', 'largefiles')
diff --git a/rhodecode/lib/vcs/backends/hg/commit.py b/rhodecode/lib/vcs/backends/hg/commit.py
--- a/rhodecode/lib/vcs/backends/hg/commit.py
+++ b/rhodecode/lib/vcs/backends/hg/commit.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -19,21 +19,26 @@
"""
HG commit module
"""
-
import os
+import logging
from zope.cachedescriptors.property import Lazy as LazyProperty
from rhodecode.lib.datelib import utcdate_fromtimestamp
from rhodecode.lib.str_utils import safe_bytes, safe_str
-from rhodecode.lib.vcs import path as vcspath
from rhodecode.lib.vcs.backends import base
from rhodecode.lib.vcs.exceptions import CommitError
from rhodecode.lib.vcs.nodes import (
- AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
- NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
- LargeFileNode)
-from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
+ DirNode,
+ FileNode,
+ NodeKind,
+ RootNode,
+ SubModuleNode,
+ LargeFileNode,
+)
+from rhodecode.lib.vcs_common import FILEMODE_LINK
+
+log = logging.getLogger(__name__)
class MercurialCommit(base.BaseCommit):
@@ -59,13 +64,13 @@ class MercurialCommit(base.BaseCommit):
# caches
self.nodes = {}
- self._stat_modes = {} # stat info for paths
+ self._path_mode_cache = {} # path stats cache, e.g filemode etc
+ self._path_type_cache = {} # path type dir/file/link etc cache
def _set_bulk_properties(self, pre_load):
if not pre_load:
return
- pre_load = [entry for entry in pre_load
- if entry not in self._filter_pre_load]
+ pre_load = [entry for entry in pre_load if entry not in self._filter_pre_load]
if not pre_load:
return
@@ -86,8 +91,7 @@ class MercurialCommit(base.BaseCommit):
@LazyProperty
def tags(self):
- tags = [name for name, commit_id in self.repository.tags.items()
- if commit_id == self.raw_id]
+ tags = [name for name, commit_id in self.repository.tags.items() if commit_id == self.raw_id]
return tags
@LazyProperty
@@ -96,9 +100,7 @@ class MercurialCommit(base.BaseCommit):
@LazyProperty
def bookmarks(self):
- bookmarks = [
- name for name, commit_id in self.repository.bookmarks.items()
- if commit_id == self.raw_id]
+ bookmarks = [name for name, commit_id in self.repository.bookmarks.items() if commit_id == self.raw_id]
return bookmarks
@LazyProperty
@@ -122,27 +124,13 @@ class MercurialCommit(base.BaseCommit):
"""
Returns modified, added, removed, deleted files for current commit
"""
- return self._remote.ctx_status(self.raw_id)
-
- @LazyProperty
- def _file_paths(self):
- return self._remote.ctx_list(self.raw_id)
-
- @LazyProperty
- def _dir_paths(self):
- dir_paths = ['']
- dir_paths.extend(list(set(get_dirs_for_path(*self._file_paths))))
-
- return dir_paths
-
- @LazyProperty
- def _paths(self):
- return self._dir_paths + self._file_paths
+ modified, added, deleted, *_ = self._remote.ctx_status(self.raw_id)
+ return modified, added, deleted
@LazyProperty
def id(self):
if self.last:
- return 'tip'
+ return "tip"
return self.short_id
@LazyProperty
@@ -150,8 +138,7 @@ class MercurialCommit(base.BaseCommit):
return self.raw_id[:12]
def _make_commits(self, commit_ids, pre_load=None):
- return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
- for commit_id in commit_ids]
+ return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in commit_ids]
@LazyProperty
def parents(self):
@@ -163,10 +150,10 @@ class MercurialCommit(base.BaseCommit):
def _get_phase_text(self, phase_id):
return {
- 0: 'public',
- 1: 'draft',
- 2: 'secret',
- }.get(phase_id) or ''
+ 0: "public",
+ 1: "draft",
+ 2: "secret",
+ }.get(phase_id) or ""
@LazyProperty
def phase(self):
@@ -195,17 +182,14 @@ class MercurialCommit(base.BaseCommit):
def _get_kind(self, path):
path = self._fix_path(path)
- if path in self._file_paths:
- return NodeKind.FILE
- elif path in self._dir_paths:
- return NodeKind.DIR
- else:
- raise CommitError(f"Node does not exist at the given path '{path}'")
+ path_type = self._get_path_type(path)
+ return path_type
- def _assert_is_path(self, path) -> str:
+ def _assert_is_path(self, path) -> str | bytes:
path = self._fix_path(path)
+
if self._get_kind(path) != NodeKind.FILE:
- raise CommitError(f"File does not exist for commit {self.raw_id} at '{path}'")
+ raise CommitError(f"File at path={path} does not exist for commit {self.raw_id}")
return path
@@ -214,20 +198,17 @@ class MercurialCommit(base.BaseCommit):
Returns stat mode of the file at the given ``path``.
"""
path = self._assert_is_path(path)
+ if path not in self._path_mode_cache:
+ self._path_mode_cache[path] = self._remote.fctx_flags(self.raw_id, path)
- if path not in self._stat_modes:
- self._stat_modes[path] = self._remote.fctx_flags(self.raw_id, path)
+ return self._path_mode_cache[path]
- if 'x' in self._stat_modes[path]:
- return base.FILEMODE_EXECUTABLE
- return base.FILEMODE_DEFAULT
+ def is_link(self, path: bytes):
+ path = self._assert_is_path(path)
+ if path not in self._path_mode_cache:
+ self._path_mode_cache[path] = self._remote.fctx_flags(self.raw_id, path)
- def is_link(self, path):
- path = self._assert_is_path(path)
- if path not in self._stat_modes:
- self._stat_modes[path] = self._remote.fctx_flags(self.raw_id, path)
-
- return 'l' in self._stat_modes[path]
+ return self._path_mode_cache[path] == FILEMODE_LINK
def is_node_binary(self, path):
path = self._assert_is_path(path)
@@ -246,7 +227,7 @@ class MercurialCommit(base.BaseCommit):
def get_file_content_streamed(self, path):
path = self._assert_is_path(path)
- stream_method = getattr(self._remote, 'stream:fctx_node_data')
+ stream_method = getattr(self._remote, "stream:fctx_node_data")
return stream_method(self.raw_id, path)
def get_file_size(self, path):
@@ -262,10 +243,8 @@ class MercurialCommit(base.BaseCommit):
for which file at given ``path`` has been modified.
"""
path = self._assert_is_path(path)
- hist = self._remote.node_history(self.raw_id, path, limit)
- return [
- self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
- for commit_id in hist]
+ history = self._remote.node_history(self.raw_id, path, limit)
+ return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in history]
def get_file_annotate(self, path, pre_load=None):
"""
@@ -276,11 +255,13 @@ class MercurialCommit(base.BaseCommit):
for ln_no, commit_id, content in result:
yield (
- ln_no, commit_id,
+ ln_no,
+ commit_id,
lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
- content)
+ content,
+ )
- def get_nodes(self, path, pre_load=None):
+ def get_nodes(self, path: bytes, pre_load=None):
"""
Returns combined ``DirNode`` and ``FileNode`` objects list representing
state of commit at the given ``path``. If node at the given ``path``
@@ -288,59 +269,92 @@ class MercurialCommit(base.BaseCommit):
"""
if self._get_kind(path) != NodeKind.DIR:
- raise CommitError(
- f"Directory does not exist for idx {self.raw_id} at '{path}'")
+ raise CommitError(f"Directory does not exist for idx {self.raw_id} at '{path}'")
path = self._fix_path(path)
- filenodes = [
- FileNode(safe_bytes(f), commit=self, pre_load=pre_load) for f in self._file_paths
- if os.path.dirname(f) == path]
- # TODO: johbo: Check if this can be done in a more obvious way
- dirs = path == '' and '' or [
- d for d in self._dir_paths
- if d and vcspath.dirname(d) == path]
- dirnodes = [
- DirNode(safe_bytes(d), commit=self) for d in dirs
- if os.path.dirname(d) == path]
+ path_nodes = []
+
+ for obj_path, node_kind, flags, pre_load_data in self._remote.get_nodes(self.raw_id, path, pre_load):
+
+ if node_kind is None:
+ raise CommitError(f"Requested object type={node_kind} cannot be mapped to a proper type")
+
+ stat_ = flags
+ # cache file mode
+ if obj_path not in self._path_mode_cache:
+ self._path_mode_cache[obj_path] = stat_
+
+ # cache type
+ if node_kind not in self._path_type_cache:
+ self._path_type_cache[obj_path] = node_kind
- alias = self.repository.alias
- for k, vals in self._submodules.items():
- if vcspath.dirname(k) == path:
- loc = vals[0]
- commit = vals[1]
- dirnodes.append(SubModuleNode(k, url=loc, commit=commit, alias=alias))
+ entry = None
+ if obj_path in self.nodes:
+ entry = self.nodes[obj_path]
+ else:
+ if node_kind == NodeKind.DIR:
+ entry = DirNode(safe_bytes(obj_path), commit=self)
+ elif node_kind == NodeKind.FILE:
+ entry = FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load, pre_load_data=pre_load_data)
+ if entry:
+ self.nodes[obj_path] = entry
+ path_nodes.append(entry)
- nodes = dirnodes + filenodes
- for node in nodes:
- if node.path not in self.nodes:
- self.nodes[node.path] = node
- nodes.sort()
+ for obj_path, (location, commit, scm_type) in self._submodules.items():
- return nodes
+ if os.path.dirname(obj_path) == path:
+ entry = SubModuleNode(obj_path, url=location, commit=commit, alias=scm_type)
+ self.nodes[obj_path] = entry
+ path_nodes.append(entry)
- def get_node(self, path, pre_load=None):
+ path_nodes.sort()
+ return path_nodes
+
+ def get_node(self, path: bytes, pre_load=None):
"""
Returns `Node` object from the given `path`. If there is no node at
the given `path`, `NodeDoesNotExistError` would be raised.
"""
path = self._fix_path(path)
- if path not in self.nodes:
- if path in self._file_paths:
+ # use cached, if we have one
+ if path in self.nodes:
+ return self.nodes[path]
+
+ path_type = self._get_path_type(path)
+ if path == b"":
+ node = RootNode(commit=self)
+ else:
+ if path_type == NodeKind.DIR:
+ node = DirNode(safe_bytes(path), commit=self)
+ elif path_type == NodeKind.FILE:
node = FileNode(safe_bytes(path), commit=self, pre_load=pre_load)
- elif path in self._dir_paths:
- if path == '':
- node = RootNode(commit=self)
- else:
- node = DirNode(safe_bytes(path), commit=self)
+ self._path_mode_cache[path] = node.mode
else:
raise self.no_node_at_path(path)
-
- # cache node
- self.nodes[path] = node
+ # cache node
+ self.nodes[path] = node
return self.nodes[path]
- def get_largefile_node(self, path):
+ def _get_path_type(self, path: bytes):
+ if path in self._path_type_cache:
+ return self._path_type_cache[path]
+
+ if path == b"":
+ self._path_type_cache[b""] = NodeKind.DIR
+ return NodeKind.DIR
+
+ path_type, flags = self._remote.get_path_type(self.raw_id, path)
+
+ if not path_type:
+ raise self.no_node_at_path(path)
+
+ self._path_type_cache[path] = path_type
+ self._path_mode_cache[path] = flags
+
+ return self._path_type_cache[path]
+
+ def get_largefile_node(self, path: bytes):
pointer_spec = self._remote.is_large_file(self.raw_id, path)
if pointer_spec:
# content of that file regular FileNode is the hash of largefile
@@ -363,40 +377,20 @@ class MercurialCommit(base.BaseCommit):
return self._remote.ctx_substate(self.raw_id)
@LazyProperty
- def affected_files(self):
+ def affected_files(self) -> list[bytes]:
"""
Gets a fast accessible file changes for given commit
"""
return self._remote.ctx_files(self.raw_id)
- @property
- def added(self):
- """
- Returns list of added ``FileNode`` objects.
- """
- return AddedFileNodesGenerator(self.added_paths, self)
-
@LazyProperty
def added_paths(self):
return [n for n in self.status[1]]
- @property
- def changed(self):
- """
- Returns list of modified ``FileNode`` objects.
- """
- return ChangedFileNodesGenerator(self.changed_paths, self)
-
@LazyProperty
def changed_paths(self):
return [n for n in self.status[0]]
- @property
- def removed(self):
- """
- Returns list of removed ``FileNode`` objects.
- """
- return RemovedFileNodesGenerator(self.removed_paths, self)
@LazyProperty
def removed_paths(self):
diff --git a/rhodecode/lib/vcs/backends/hg/diff.py b/rhodecode/lib/vcs/backends/hg/diff.py
--- a/rhodecode/lib/vcs/backends/hg/diff.py
+++ b/rhodecode/lib/vcs/backends/hg/diff.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/backends/hg/inmemory.py b/rhodecode/lib/vcs/backends/hg/inmemory.py
--- a/rhodecode/lib/vcs/backends/hg/inmemory.py
+++ b/rhodecode/lib/vcs/backends/hg/inmemory.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/backends/hg/repository.py b/rhodecode/lib/vcs/backends/hg/repository.py
--- a/rhodecode/lib/vcs/backends/hg/repository.py
+++ b/rhodecode/lib/vcs/backends/hg/repository.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -368,26 +368,32 @@ class MercurialRepository(BaseRepository
be created.
If `src_url` is given, would try to clone repository from the
- location at given clone_point. Additionally it'll make update to
+ location at given clone_point. Additionally, it'll make update to
working copy accordingly to `do_workspace_checkout` flag.
"""
if create and os.path.exists(self.path):
raise RepositoryError(
f"Cannot create repository at {self.path}, location already exist")
- if src_url:
- url = str(self._get_url(src_url))
- MercurialRepository.check_url(url, self.config)
+ if create:
+ if src_url:
+ url = str(self._get_url(src_url))
+ MercurialRepository.check_url(url, self.config)
- self._remote.clone(url, self.path, do_workspace_checkout)
+ self._remote.clone(url, self.path, do_workspace_checkout)
- # Don't try to create if we've already cloned repo
- create = False
+ # Don't try to create if we've already cloned repo
+ create = False
+ self._remote.localrepository(create)
+ else:
+ os.makedirs(self.path, mode=0o755)
+ create = True
+ self._remote.localrepository(create)
- if create:
- os.makedirs(self.path, mode=0o755)
-
- self._remote.localrepository(create)
+ else:
+ if not self._remote.assert_correct_path():
+ raise RepositoryError(
+ f'Path "{self.path}" does not contain a Mercurial repository')
@LazyProperty
def in_memory_commit(self):
@@ -444,7 +450,7 @@ class MercurialRepository(BaseRepository
return os.path.join(self.path, '.hg', '.hgrc')
def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
- translate_tag=None, maybe_unreachable=False, reference_obj=None):
+ translate_tag=None, maybe_unreachable=False, reference_obj=None) -> MercurialCommit:
"""
Returns ``MercurialCommit`` object representing repository's
commit at the given `commit_id` or `commit_idx`.
@@ -592,8 +598,7 @@ class MercurialRepository(BaseRepository
"""
Create a local clone of the current repo.
"""
- self._remote.clone(self.path, clone_path, update_after_clone=True,
- hooks=False)
+ self._remote.clone(self.path, clone_path, update_after_clone=True, hooks=False)
def _update(self, revision, clean=False):
"""
diff --git a/rhodecode/lib/vcs/backends/svn/__init__.py b/rhodecode/lib/vcs/backends/svn/__init__.py
--- a/rhodecode/lib/vcs/backends/svn/__init__.py
+++ b/rhodecode/lib/vcs/backends/svn/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/backends/svn/commit.py b/rhodecode/lib/vcs/backends/svn/commit.py
--- a/rhodecode/lib/vcs/backends/svn/commit.py
+++ b/rhodecode/lib/vcs/backends/svn/commit.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -19,8 +19,7 @@
"""
SVN commit module
"""
-
-
+import logging
import dateutil.parser
from zope.cachedescriptors.property import Lazy as LazyProperty
@@ -28,9 +27,10 @@ from rhodecode.lib.str_utils import safe
from rhodecode.lib.vcs import nodes, path as vcspath
from rhodecode.lib.vcs.backends import base
from rhodecode.lib.vcs.exceptions import CommitError
-
+from vcsserver.lib.vcs_common import NodeKind, FILEMODE_EXECUTABLE, FILEMODE_DEFAULT, FILEMODE_LINK
+_SVN_PROP_TRUE = "*"
-_SVN_PROP_TRUE = '*'
+log = logging.getLogger(__name__)
class SubversionCommit(base.BaseCommit):
@@ -53,15 +53,16 @@ class SubversionCommit(base.BaseCommit):
# which knows how to translate commit index and commit id
self.raw_id = commit_id
self.short_id = commit_id
- self.id = f'r{commit_id}'
+ self.id = f"r{commit_id}"
- # TODO: Implement the following placeholder attributes
self.nodes = {}
+ self._path_mode_cache = {} # path stats cache, e.g filemode etc
+ self._path_type_cache = {} # path type dir/file/link etc cache
self.tags = []
@property
def author(self):
- return safe_str(self._properties.get('svn:author'))
+ return safe_str(self._properties.get("svn:author"))
@property
def date(self):
@@ -69,7 +70,7 @@ class SubversionCommit(base.BaseCommit):
@property
def message(self):
- return safe_str(self._properties.get('svn:log'))
+ return safe_str(self._properties.get("svn:log"))
@LazyProperty
def _properties(self):
@@ -91,19 +92,46 @@ class SubversionCommit(base.BaseCommit):
return [child]
return []
- def get_file_mode(self, path: bytes):
+ def _calculate_file_mode(self, path: bytes):
# Note: Subversion flags files which are executable with a special
# property `svn:executable` which is set to the value ``"*"``.
- if self._get_file_property(path, 'svn:executable') == _SVN_PROP_TRUE:
- return base.FILEMODE_EXECUTABLE
+ if self._get_file_property(path, "svn:executable") == _SVN_PROP_TRUE:
+ return FILEMODE_EXECUTABLE
else:
- return base.FILEMODE_DEFAULT
+ return FILEMODE_DEFAULT
+
+ def get_file_mode(self, path: bytes):
+ path = self._fix_path(path)
+
+ if path not in self._path_mode_cache:
+ self._path_mode_cache[path] = self._calculate_file_mode(path)
+
+ return self._path_mode_cache[path]
+
+ def _get_path_type(self, path: bytes):
+ if path in self._path_type_cache:
+ return self._path_type_cache[path]
- def is_link(self, path):
+ if path == b"":
+ self._path_type_cache[b""] = NodeKind.DIR
+ return NodeKind.DIR
+
+ path_type = self._remote.get_node_type(self._svn_rev, path)
+
+ if not path_type:
+ raise self.no_node_at_path(path)
+
+ #flags = None
+ self._path_type_cache[path] = path_type
+ #self._path_mode_cache[path] = flags
+
+ return self._path_type_cache[path]
+
+ def is_link(self, path: bytes):
# Note: Subversion has a flag for special files, the content of the
# file contains the type of that file.
- if self._get_file_property(path, 'svn:special') == _SVN_PROP_TRUE:
- return self.get_file_content(path).startswith(b'link')
+ if self._get_file_property(path, "svn:special") == _SVN_PROP_TRUE:
+ return self.get_file_content(path).startswith(b"link")
return False
def is_node_binary(self, path):
@@ -115,8 +143,7 @@ class SubversionCommit(base.BaseCommit):
return self._remote.md5_hash(self._svn_rev, safe_str(path))
def _get_file_property(self, path, name):
- file_properties = self._remote.node_properties(
- safe_str(path), self._svn_rev)
+ file_properties = self._remote.node_properties(safe_str(path), self._svn_rev)
return file_properties.get(name)
def get_file_content(self, path):
@@ -126,7 +153,7 @@ class SubversionCommit(base.BaseCommit):
def get_file_content_streamed(self, path):
path = self._fix_path(path)
- stream_method = getattr(self._remote, 'stream:get_file_content')
+ stream_method = getattr(self._remote, "stream:get_file_content")
return stream_method(self._svn_rev, safe_str(path))
def get_file_size(self, path):
@@ -134,11 +161,9 @@ class SubversionCommit(base.BaseCommit):
return self._remote.get_file_size(self._svn_rev, safe_str(path))
def get_path_history(self, path, limit=None, pre_load=None):
- path = safe_str(self._fix_path(path))
- history = self._remote.node_history(path, self._svn_rev, limit)
- return [
- self.repository.get_commit(commit_id=str(svn_rev))
- for svn_rev in history]
+ path = self._fix_path(path)
+ history = self._remote.node_history(self._svn_rev, safe_str(path), limit)
+ return [self.repository.get_commit(commit_id=str(svn_rev)) for svn_rev in history]
def get_file_annotate(self, path, pre_load=None):
result = self._remote.file_annotate(safe_str(path), self._svn_rev)
@@ -146,67 +171,78 @@ class SubversionCommit(base.BaseCommit):
for zero_based_line_no, svn_rev, content in result:
commit_id = str(svn_rev)
line_no = zero_based_line_no + 1
- yield (
- line_no,
- commit_id,
- lambda: self.repository.get_commit(commit_id=commit_id),
- content)
+ yield line_no, commit_id, lambda: self.repository.get_commit(commit_id=commit_id), content
- def get_node(self, path, pre_load=None):
+ def get_node(self, path: bytes, pre_load=None):
path = self._fix_path(path)
- if path not in self.nodes:
+
+ # use cached, if we have one
+ if path in self.nodes:
+ return self.nodes[path]
- if path == '':
- node = nodes.RootNode(commit=self)
+ path_type = self._get_path_type(path)
+ if path == b"":
+ node = nodes.RootNode(commit=self)
+ else:
+ if path_type == NodeKind.DIR:
+ node = nodes.DirNode(safe_bytes(path), commit=self)
+ elif path_type == NodeKind.FILE:
+ node = nodes.FileNode(safe_bytes(path), commit=self, pre_load=pre_load)
+ self._path_mode_cache[path] = node.mode
else:
- node_type = self._remote.get_node_type(self._svn_rev, safe_str(path))
- if node_type == 'dir':
- node = nodes.DirNode(safe_bytes(path), commit=self)
- elif node_type == 'file':
- node = nodes.FileNode(safe_bytes(path), commit=self, pre_load=pre_load)
- else:
- raise self.no_node_at_path(path)
+ raise self.no_node_at_path(path)
- self.nodes[path] = node
+ self.nodes[path] = node
return self.nodes[path]
- def get_nodes(self, path, pre_load=None):
+ def get_nodes(self, path: bytes, pre_load=None):
if self._get_kind(path) != nodes.NodeKind.DIR:
- raise CommitError(
- f"Directory does not exist for commit {self.raw_id} at '{path}'")
- path = safe_str(self._fix_path(path))
+ raise CommitError(f"Directory does not exist for commit {self.raw_id} at '{path}'")
+ path = self._fix_path(path)
path_nodes = []
- for name, kind in self._remote.get_nodes(self._svn_rev, path):
- node_path = vcspath.join(path, name)
- if kind == 'dir':
- node = nodes.DirNode(safe_bytes(node_path), commit=self)
- elif kind == 'file':
- node = nodes.FileNode(safe_bytes(node_path), commit=self, pre_load=pre_load)
+
+ for obj_path, node_kind, pre_load_data in self._remote.get_nodes(self._svn_rev, path, pre_load):
+
+ if node_kind is None:
+ raise CommitError(f"Requested object type={node_kind} cannot be determined")
+
+ # TODO: implement it ??
+ stat_ = None
+ # # cache file mode
+ # if obj_path not in self._path_mode_cache:
+ # self._path_mode_cache[obj_path] = stat_
+
+ # cache type
+ if node_kind not in self._path_type_cache:
+ self._path_type_cache[obj_path] = node_kind
+
+ entry = None
+ if obj_path in self.nodes:
+ entry = self.nodes[obj_path]
else:
- raise ValueError(f"Node kind {kind} not supported.")
- self.nodes[node_path] = node
- path_nodes.append(node)
+ if node_kind == NodeKind.DIR:
+ entry = nodes.DirNode(safe_bytes(obj_path), commit=self)
+ elif node_kind == NodeKind.FILE:
+ entry = nodes.FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load, pre_load_data=pre_load_data)
+ if entry:
+ self.nodes[obj_path] = entry
+ path_nodes.append(entry)
+ path_nodes.sort()
return path_nodes
def _get_kind(self, path):
path = self._fix_path(path)
- kind = self._remote.get_node_type(self._svn_rev, path)
- if kind == 'file':
- return nodes.NodeKind.FILE
- elif kind == 'dir':
- return nodes.NodeKind.DIR
- else:
- raise CommitError(
- f"Node does not exist at the given path '{path}'")
+ path_type = self._get_path_type(path)
+ return path_type
@LazyProperty
def _changes_cache(self):
return self._remote.revision_changes(self._svn_rev)
@LazyProperty
- def affected_files(self):
+ def affected_files(self) -> list[bytes]:
changed_files = set()
for files in self._changes_cache.values():
changed_files.update(files)
@@ -216,29 +252,17 @@ class SubversionCommit(base.BaseCommit):
def id(self):
return self.raw_id
- @property
- def added(self):
- return nodes.AddedFileNodesGenerator(self.added_paths, self)
-
@LazyProperty
def added_paths(self):
- return [n for n in self._changes_cache['added']]
-
- @property
- def changed(self):
- return nodes.ChangedFileNodesGenerator(self.changed_paths, self)
+ return [n for n in self._changes_cache["added"]]
@LazyProperty
def changed_paths(self):
- return [n for n in self._changes_cache['changed']]
-
- @property
- def removed(self):
- return nodes.RemovedFileNodesGenerator(self.removed_paths, self)
+ return [n for n in self._changes_cache["changed"]]
@LazyProperty
def removed_paths(self):
- return [n for n in self._changes_cache['removed']]
+ return [n for n in self._changes_cache["removed"]]
def _date_from_svn_properties(properties):
@@ -248,7 +272,7 @@ def _date_from_svn_properties(properties
:return: :class:`datetime.datetime` instance. The object is naive.
"""
- aware_date = dateutil.parser.parse(properties.get('svn:date'))
+ aware_date = dateutil.parser.parse(properties.get("svn:date"))
# final_date = aware_date.astimezone(dateutil.tz.tzlocal())
final_date = aware_date
return final_date.replace(tzinfo=None)
diff --git a/rhodecode/lib/vcs/backends/svn/diff.py b/rhodecode/lib/vcs/backends/svn/diff.py
--- a/rhodecode/lib/vcs/backends/svn/diff.py
+++ b/rhodecode/lib/vcs/backends/svn/diff.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/backends/svn/inmemory.py b/rhodecode/lib/vcs/backends/svn/inmemory.py
--- a/rhodecode/lib/vcs/backends/svn/inmemory.py
+++ b/rhodecode/lib/vcs/backends/svn/inmemory.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/backends/svn/repository.py b/rhodecode/lib/vcs/backends/svn/repository.py
--- a/rhodecode/lib/vcs/backends/svn/repository.py
+++ b/rhodecode/lib/vcs/backends/svn/repository.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -30,7 +30,7 @@ from zope.cachedescriptors.property impo
from collections import OrderedDict
from rhodecode.lib.datelib import date_astimestamp
-from rhodecode.lib.str_utils import safe_str
+from rhodecode.lib.str_utils import safe_str, safe_bytes
from rhodecode.lib.utils2 import CachedProperty
from rhodecode.lib.vcs import connection, path as vcspath
from rhodecode.lib.vcs.backends import base
@@ -157,16 +157,18 @@ class SubversionRepository(base.BaseRepo
for pattern in self._patterns_from_section(config_section):
pattern = vcspath.sanitize(pattern)
+ bytes_pattern = safe_bytes(pattern)
+
tip = self.get_commit()
try:
- if pattern.endswith('*'):
- basedir = tip.get_node(vcspath.dirname(pattern))
+ if bytes_pattern.endswith(b'*'):
+ basedir = tip.get_node(vcspath.dirname(bytes_pattern))
directories = basedir.dirs
else:
- directories = (tip.get_node(pattern), )
+ directories = (tip.get_node(bytes_pattern), )
except NodeDoesNotExistError:
continue
- found_items.update((safe_str(n.path), self.commit_ids[-1]) for n in directories)
+ found_items.update((dir_node.str_path, self.commit_ids[-1]) for dir_node in directories)
def get_name(item):
return item[0]
@@ -216,7 +218,7 @@ class SubversionRepository(base.BaseRepo
def _get_commit_idx(self, commit_id):
try:
svn_rev = int(commit_id)
- except:
+ except Exception:
# TODO: johbo: this might be only one case, HEAD, check this
svn_rev = self._remote.lookup(commit_id)
commit_idx = svn_rev - 1
@@ -321,8 +323,7 @@ class SubversionRepository(base.BaseRepo
# TODO: johbo: Reconsider impact of DEFAULT_BRANCH_NAME here
if branch_name not in [None, self.DEFAULT_BRANCH_NAME]:
svn_rev = int(self.commit_ids[-1])
- commit_ids = self._remote.node_history(
- path=branch_name, revision=svn_rev, limit=None)
+ commit_ids = self._remote.node_history(svn_rev, branch_name, None)
commit_ids = [str(i) for i in reversed(commit_ids)]
if start_pos or end_pos:
diff --git a/rhodecode/lib/vcs/client_http.py b/rhodecode/lib/vcs/client_http.py
--- a/rhodecode/lib/vcs/client_http.py
+++ b/rhodecode/lib/vcs/client_http.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -27,14 +27,13 @@ import time
import urllib.request
import urllib.error
import urllib.parse
-import urllib.parse
import uuid
import traceback
import pycurl
import msgpack
import requests
-from requests.packages.urllib3.util.retry import Retry
+from urllib3.util.retry import Retry
import rhodecode
from rhodecode.lib import rc_cache
@@ -216,7 +215,7 @@ class RemoteRepo(object):
self._cache_region, self._cache_namespace = \
remote_maker.init_cache_region(cache_repo_id)
- with_wire = with_wire or {}
+ with_wire = with_wire or {"cache": False}
repo_state_uid = with_wire.get('repo_state_uid') or 'state'
@@ -287,13 +286,15 @@ class RemoteRepo(object):
'fctx_size', 'stream:fctx_node_data', 'blob_raw_length',
'node_history',
'revision', 'tree_items',
- 'ctx_list', 'ctx_branch', 'ctx_description',
+ 'ctx_branch', 'ctx_description',
'bulk_request',
'assert_correct_path',
'is_path_valid_repository',
]
- if local_cache_on and name in cache_methods:
+ wire_cache = self._wire['cache']
+
+ if local_cache_on and wire_cache and name in cache_methods:
cache_on = True
repo_state_uid = self._wire['repo_state_uid']
call_args = [a for a in args]
@@ -303,6 +304,7 @@ class RemoteRepo(object):
@exceptions.map_vcs_exceptions
def _call(self, name, *args, **kwargs):
+
context_uid, payload = self._base_call(name, *args, **kwargs)
url = self.url
diff --git a/rhodecode/lib/vcs/compat.py b/rhodecode/lib/vcs/compat.py
--- a/rhodecode/lib/vcs/compat.py
+++ b/rhodecode/lib/vcs/compat.py
@@ -1,1 +1,1 @@
-from pyramid.compat import configparser
\ No newline at end of file
+from pyramid.compat import configparser
diff --git a/rhodecode/lib/vcs/conf/__init__.py b/rhodecode/lib/vcs/conf/__init__.py
--- a/rhodecode/lib/vcs/conf/__init__.py
+++ b/rhodecode/lib/vcs/conf/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/conf/mtypes.py b/rhodecode/lib/vcs/conf/mtypes.py
--- a/rhodecode/lib/vcs/conf/mtypes.py
+++ b/rhodecode/lib/vcs/conf/mtypes.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/conf/settings.py b/rhodecode/lib/vcs/conf/settings.py
--- a/rhodecode/lib/vcs/conf/settings.py
+++ b/rhodecode/lib/vcs/conf/settings.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,9 +20,6 @@
Internal settings for vcs-lib
"""
-# list of default encoding used in safe_str methods
-DEFAULT_ENCODINGS = ['utf8']
-
# Compatibility version when creating SVN repositories. None means newest.
# Other available options are: pre-1.4-compatible, pre-1.5-compatible,
diff --git a/rhodecode/lib/vcs/connection.py b/rhodecode/lib/vcs/connection.py
--- a/rhodecode/lib/vcs/connection.py
+++ b/rhodecode/lib/vcs/connection.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/exceptions.py b/rhodecode/lib/vcs/exceptions.py
--- a/rhodecode/lib/vcs/exceptions.py
+++ b/rhodecode/lib/vcs/exceptions.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -102,10 +102,6 @@ class NodeError(VCSError):
pass
-class RemovedFileNodeError(NodeError):
- pass
-
-
class NodeAlreadyExistsError(CommittingError):
pass
diff --git a/rhodecode/lib/vcs/geventcurl.py b/rhodecode/lib/vcs/geventcurl.py
--- a/rhodecode/lib/vcs/geventcurl.py
+++ b/rhodecode/lib/vcs/geventcurl.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/nodes.py b/rhodecode/lib/vcs/nodes.py
--- a/rhodecode/lib/vcs/nodes.py
+++ b/rhodecode/lib/vcs/nodes.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -19,6 +19,7 @@
"""
Module holding everything related to vcs nodes, with vcs2 architecture.
"""
+
import functools
import os
import stat
@@ -29,83 +30,25 @@ from rhodecode.config.conf import LANGUA
from rhodecode.lib.str_utils import safe_str, safe_bytes
from rhodecode.lib.hash_utils import md5
from rhodecode.lib.vcs import path as vcspath
-from rhodecode.lib.vcs.backends.base import EmptyCommit, FILEMODE_DEFAULT
+from rhodecode.lib.vcs.backends.base import EmptyCommit
from rhodecode.lib.vcs.conf.mtypes import get_mimetypes_db
-from rhodecode.lib.vcs.exceptions import NodeError, RemovedFileNodeError
-
-LARGEFILE_PREFIX = '.hglf'
+from rhodecode.lib.vcs.exceptions import NodeError
+from rhodecode.lib.vcs_common import NodeKind, FILEMODE_DEFAULT
-
-class NodeKind:
- SUBMODULE = -1
- DIR = 1
- FILE = 2
- LARGEFILE = 3
+LARGEFILE_PREFIX = ".hglf"
class NodeState:
- ADDED = 'added'
- CHANGED = 'changed'
- NOT_CHANGED = 'not changed'
- REMOVED = 'removed'
-
-#TODO: not sure if that should be bytes or str ?
-# most probably bytes because content should be bytes and we check it
-BIN_BYTE_MARKER = b'\0'
+ ADDED = "added"
+ CHANGED = "changed"
+ NOT_CHANGED = "not changed"
+ REMOVED = "removed"
-class NodeGeneratorBase(object):
- """
- Base class for removed added and changed filenodes, it's a lazy generator
- class that will create filenodes only on iteration or call
-
- The len method doesn't need to create filenodes at all
- """
-
- def __init__(self, current_paths, cs):
- self.cs = cs
- self.current_paths = current_paths
-
- def __call__(self):
- return [n for n in self]
-
- def __getitem__(self, key):
- if isinstance(key, slice):
- for p in self.current_paths[key.start:key.stop]:
- yield self.cs.get_node(p)
-
- def __len__(self):
- return len(self.current_paths)
+# TODO: not sure if that should be bytes or str ?
+# most probably bytes because content should be bytes and we check it
+BIN_BYTE_MARKER = b"\0"
- def __iter__(self):
- for p in self.current_paths:
- yield self.cs.get_node(p)
-
-
-class AddedFileNodesGenerator(NodeGeneratorBase):
- """
- Class holding added files for current commit
- """
-
-
-class ChangedFileNodesGenerator(NodeGeneratorBase):
- """
- Class holding changed files for current commit
- """
-
-
-class RemovedFileNodesGenerator(NodeGeneratorBase):
- """
- Class holding removed files for current commit
- """
- def __iter__(self):
- for p in self.current_paths:
- yield RemovedFileNode(path=safe_bytes(p))
-
- def __getitem__(self, key):
- if isinstance(key, slice):
- for p in self.current_paths[key.start:key.stop]:
- yield RemovedFileNode(path=safe_bytes(p))
@functools.total_ordering
@@ -119,21 +62,22 @@ class Node(object):
only. Moreover, every single node is identified by the ``path`` attribute,
so it cannot end with slash, too. Otherwise, path could lead to mistakes.
"""
+
# RTLO marker allows swapping text, and certain
# security attacks could be used with this
- RTLO_MARKER = "\u202E"
+ RTLO_MARKER = "\u202e"
commit = None
def __init__(self, path: bytes, kind):
self._validate_path(path) # can throw exception if path is invalid
- self.bytes_path = path.rstrip(b'/') # store for __repr__
- self.path = safe_str(self.bytes_path) # we store paths as str
+ self.bytes_path: bytes = path.rstrip(b"/") # store for mixed encoding, and raw version
+ self.str_path: str = safe_str(self.bytes_path) # we store paths as str
+ self.path: str = self.str_path
- if self.bytes_path == b'' and kind != NodeKind.DIR:
- raise NodeError("Only DirNode and its subclasses may be "
- "initialized with empty path")
+ if self.bytes_path == b"" and kind != NodeKind.DIR:
+ raise NodeError("Only DirNode and its subclasses may be initialized with empty path")
self.kind = kind
if self.is_root() and not self.is_dir():
@@ -142,7 +86,7 @@ class Node(object):
def __eq__(self, other):
if type(self) is not type(other):
return False
- for attr in ['name', 'path', 'kind']:
+ for attr in ["name", "path", "kind"]:
if getattr(self, attr) != getattr(other, attr):
return False
if self.is_file():
@@ -166,22 +110,9 @@ class Node(object):
if self.path > other.path:
return False
- # def __cmp__(self, other):
- # """
- # Comparator using name of the node, needed for quick list sorting.
- # """
- #
- # kind_cmp = cmp(self.kind, other.kind)
- # if kind_cmp:
- # if isinstance(self, SubModuleNode):
- # # we make submodules equal to dirnode for "sorting" purposes
- # return NodeKind.DIR
- # return kind_cmp
- # return cmp(self.name, other.name)
-
def __repr__(self):
- maybe_path = getattr(self, 'path', 'UNKNOWN_PATH')
- return f'<{self.__class__.__name__} {maybe_path!r}>'
+ maybe_path = getattr(self, "path", "UNKNOWN_PATH")
+ return f"<{self.__class__.__name__} {maybe_path!r}>"
def __str__(self):
return self.name
@@ -189,19 +120,21 @@ class Node(object):
def _validate_path(self, path: bytes):
self._assert_bytes(path)
- if path.startswith(b'/'):
+ if path.startswith(b"/"):
raise NodeError(
f"Cannot initialize Node objects with slash at "
f"the beginning as only relative paths are supported. "
- f"Got {path}")
+ f"Got {path}"
+ )
- def _assert_bytes(self, value):
+ @classmethod
+ def _assert_bytes(cls, value):
if not isinstance(value, bytes):
raise TypeError(f"Bytes required as input, got {type(value)} of {value}.")
@LazyProperty
def parent(self):
- parent_path = self.get_parent_path()
+ parent_path: bytes = self.get_parent_path()
if parent_path:
if self.commit:
return self.commit.get_node(parent_path)
@@ -209,10 +142,6 @@ class Node(object):
return None
@LazyProperty
- def str_path(self) -> str:
- return safe_str(self.path)
-
- @LazyProperty
def has_rtlo(self):
"""Detects if a path has right-to-left-override marker"""
return self.RTLO_MARKER in self.str_path
@@ -223,10 +152,10 @@ class Node(object):
Returns name of the directory from full path of this vcs node. Empty
string is returned if there's no directory in the path
"""
- _parts = self.path.rstrip('/').rsplit('/', 1)
+ _parts = self.path.rstrip("/").rsplit("/", 1)
if len(_parts) == 2:
return _parts[0]
- return ''
+ return ""
@LazyProperty
def name(self):
@@ -234,7 +163,7 @@ class Node(object):
Returns name of the node so if its path
then only last part is returned.
"""
- return self.path.rstrip('/').split('/')[-1]
+ return self.str_path.rstrip("/").split("/")[-1]
@property
def kind(self):
@@ -242,12 +171,12 @@ class Node(object):
@kind.setter
def kind(self, kind):
- if hasattr(self, '_kind'):
+ if hasattr(self, "_kind"):
raise NodeError("Cannot change node's kind")
else:
self._kind = kind
# Post setter check (path's trailing slash)
- if self.path.endswith('/'):
+ if self.str_path.endswith("/"):
raise NodeError("Node's path cannot end with slash")
def get_parent_path(self) -> bytes:
@@ -255,8 +184,8 @@ class Node(object):
Returns node's parent path or empty string if node is root.
"""
if self.is_root():
- return b''
- str_path = vcspath.dirname(self.path.rstrip('/')) + '/'
+ return b""
+ str_path = vcspath.dirname(self.bytes_path.rstrip(b"/")) + b"/"
return safe_bytes(str_path)
@@ -278,7 +207,7 @@ class Node(object):
"""
Returns ``True`` if node is a root node and ``False`` otherwise.
"""
- return self.kind == NodeKind.DIR and self.path == ''
+ return self.kind == NodeKind.DIR and self.path == ""
def is_submodule(self):
"""
@@ -292,29 +221,13 @@ class Node(object):
Returns ``True`` if node's kind is ``NodeKind.LARGEFILE``, ``False``
otherwise
"""
- return self.kind == NodeKind.LARGEFILE
+ return self.kind == NodeKind.LARGE_FILE
def is_link(self):
if self.commit:
- return self.commit.is_link(self.path)
+ return self.commit.is_link(self.bytes_path)
return False
- @LazyProperty
- def added(self):
- return self.state is NodeState.ADDED
-
- @LazyProperty
- def changed(self):
- return self.state is NodeState.CHANGED
-
- @LazyProperty
- def not_changed(self):
- return self.state is NodeState.NOT_CHANGED
-
- @LazyProperty
- def removed(self):
- return self.state is NodeState.REMOVED
-
class FileNode(Node):
"""
@@ -325,9 +238,10 @@ class FileNode(Node):
:attribute: commit: if given, first time content is accessed, callback
:attribute: mode: stat mode for a node. Default is `FILEMODE_DEFAULT`.
"""
+
_filter_pre_load = []
- def __init__(self, path: bytes, content: bytes | None = None, commit=None, mode=None, pre_load=None):
+ def __init__(self, path: bytes, content: bytes | None = None, commit=None, mode=None, pre_load=None, pre_load_data=None):
"""
Only one of ``content`` and ``commit`` may be given. Passing both
would raise ``NodeError`` exception.
@@ -349,8 +263,10 @@ class FileNode(Node):
content = safe_bytes(content)
self._content = content
self._mode = mode or FILEMODE_DEFAULT
-
- self._set_bulk_properties(pre_load)
+ if pre_load_data:
+ self._store_pre_load(pre_load_data)
+ else:
+ self._set_bulk_properties(pre_load)
def __eq__(self, other):
eq = super().__eq__(other)
@@ -359,7 +275,7 @@ class FileNode(Node):
return self.content == other.content
def __hash__(self):
- raw_id = getattr(self.commit, 'raw_id', '')
+ raw_id = getattr(self.commit, "raw_id", "")
return hash((self.path, raw_id))
def __lt__(self, other):
@@ -369,33 +285,35 @@ class FileNode(Node):
return self.content < other.content
def __repr__(self):
- short_id = getattr(self.commit, 'short_id', '')
- return f'<{self.__class__.__name__} path={self.path!r}, short_id={short_id}>'
+ short_id = getattr(self.commit, "short_id", "")
+ return f"<{self.__class__.__name__} path={self.str_path!r}, short_id={short_id}>"
def _set_bulk_properties(self, pre_load):
if not pre_load:
return
- pre_load = [entry for entry in pre_load
- if entry not in self._filter_pre_load]
+ pre_load = [entry for entry in pre_load if entry not in self._filter_pre_load]
if not pre_load:
return
remote = self.commit.get_remote()
- result = remote.bulk_file_request(self.commit.raw_id, self.path, pre_load)
+ result = remote.bulk_file_request(self.commit.raw_id, self.bytes_path, pre_load)
- for attr, value in result.items():
+ self._store_pre_load(result.items())
+
+ def _store_pre_load(self, pre_load_data):
+ for attr, value in pre_load_data:
if attr == "flags":
- self.__dict__['mode'] = safe_str(value)
+ self.__dict__["mode"] = safe_str(value)
elif attr == "size":
- self.__dict__['size'] = value
+ self.__dict__["size"] = value
elif attr == "data":
- self.__dict__['_content'] = value
+ self.__dict__["_content"] = value
elif attr == "is_binary":
- self.__dict__['is_binary'] = value
+ self.__dict__["is_binary"] = value
elif attr == "md5":
- self.__dict__['md5'] = value
+ self.__dict__["md5"] = value
else:
- raise ValueError(f'Unsupported attr in bulk_property: {attr}')
+ raise ValueError(f"Unsupported attr in bulk_property: {attr}")
@LazyProperty
def mode(self):
@@ -404,7 +322,7 @@ class FileNode(Node):
use value given at initialization or `FILEMODE_DEFAULT` (default).
"""
if self.commit:
- mode = self.commit.get_file_mode(self.path)
+ mode = self.commit.get_file_mode(self.bytes_path)
else:
mode = self._mode
return mode
@@ -416,7 +334,7 @@ class FileNode(Node):
"""
if self.commit:
if self._content is None:
- self._content = self.commit.get_file_content(self.path)
+ self._content = self.commit.get_file_content(self.bytes_path)
content = self._content
else:
content = self._content
@@ -427,7 +345,7 @@ class FileNode(Node):
Returns lazily content of the FileNode.
"""
if self.commit:
- content = self.commit.get_file_content(self.path)
+ content = self.commit.get_file_content(self.bytes_path)
else:
content = self._content
return content
@@ -438,7 +356,7 @@ class FileNode(Node):
vcsserver without loading it to memory.
"""
if self.commit:
- return self.commit.get_file_content_streamed(self.path)
+ return self.commit.get_file_content_streamed(self.bytes_path)
raise NodeError("Cannot retrieve stream_bytes without related commit attribute")
def metadata_uncached(self):
@@ -462,7 +380,7 @@ class FileNode(Node):
"""
content = self.raw_bytes
if content and not isinstance(content, bytes):
- raise ValueError(f'Content is of type {type(content)} instead of bytes')
+ raise ValueError(f"Content is of type {type(content)} instead of bytes")
return content
@LazyProperty
@@ -472,27 +390,21 @@ class FileNode(Node):
@LazyProperty
def size(self):
if self.commit:
- return self.commit.get_file_size(self.path)
- raise NodeError(
- "Cannot retrieve size of the file without related "
- "commit attribute")
+ return self.commit.get_file_size(self.bytes_path)
+ raise NodeError("Cannot retrieve size of the file without related commit attribute")
@LazyProperty
def message(self):
if self.commit:
return self.last_commit.message
- raise NodeError(
- "Cannot retrieve message of the file without related "
- "commit attribute")
+ raise NodeError("Cannot retrieve message of the file without related " "commit attribute")
@LazyProperty
def last_commit(self):
if self.commit:
pre_load = ["author", "date", "message", "parents"]
- return self.commit.get_path_commit(self.path, pre_load=pre_load)
- raise NodeError(
- "Cannot retrieve last commit of the file without "
- "related commit attribute")
+ return self.commit.get_path_commit(self.bytes_path, pre_load=pre_load)
+ raise NodeError("Cannot retrieve last commit of the file without related commit attribute")
def get_mimetype(self):
"""
@@ -502,28 +414,27 @@ class FileNode(Node):
attribute to indicate that type should *NOT* be calculated).
"""
- if hasattr(self, '_mimetype'):
- if (isinstance(self._mimetype, (tuple, list)) and
- len(self._mimetype) == 2):
+ if hasattr(self, "_mimetype"):
+ if isinstance(self._mimetype, (tuple, list)) and len(self._mimetype) == 2:
return self._mimetype
else:
- raise NodeError('given _mimetype attribute must be an 2 '
- 'element list or tuple')
+ raise NodeError("given _mimetype attribute must be an 2 element list or tuple")
db = get_mimetypes_db()
mtype, encoding = db.guess_type(self.name)
if mtype is None:
if not self.is_largefile() and self.is_binary:
- mtype = 'application/octet-stream'
+ mtype = "application/octet-stream"
encoding = None
else:
- mtype = 'text/plain'
+ mtype = "text/plain"
encoding = None
# try with pygments
try:
from pygments.lexers import get_lexer_for_filename
+
mt = get_lexer_for_filename(self.name).mimetypes
except Exception:
mt = None
@@ -544,18 +455,17 @@ class FileNode(Node):
@LazyProperty
def mimetype_main(self):
- return self.mimetype.split('/')[0]
+ return self.mimetype.split("/")[0]
@classmethod
def get_lexer(cls, filename, content=None):
from pygments import lexers
- extension = filename.split('.')[-1]
+ extension = filename.split(".")[-1]
lexer = None
try:
- lexer = lexers.guess_lexer_for_filename(
- filename, content, stripnl=False)
+ lexer = lexers.guess_lexer_for_filename(filename, content, stripnl=False)
except lexers.ClassNotFound:
pass
@@ -580,7 +490,7 @@ class FileNode(Node):
content, name and mimetype.
"""
# TODO: this is more proper, but super heavy on investigating the type based on the content
- #self.get_lexer(self.name, self.content)
+ # self.get_lexer(self.name, self.content)
return self.get_lexer(self.name)
@@ -597,8 +507,8 @@ class FileNode(Node):
Returns a list of commit for this file in which the file was changed
"""
if self.commit is None:
- raise NodeError('Unable to get commit for this FileNode')
- return self.commit.get_path_history(self.path)
+ raise NodeError("Unable to get commit for this FileNode")
+ return self.commit.get_path_history(self.bytes_path)
@LazyProperty
def annotate(self):
@@ -606,22 +516,9 @@ class FileNode(Node):
Returns a list of three element tuples with lineno, commit and line
"""
if self.commit is None:
- raise NodeError('Unable to get commit for this FileNode')
+ raise NodeError("Unable to get commit for this FileNode")
pre_load = ["author", "date", "message", "parents"]
- return self.commit.get_file_annotate(self.path, pre_load=pre_load)
-
- @LazyProperty
- def state(self):
- if not self.commit:
- raise NodeError(
- "Cannot check state of the node if it's not "
- "linked with commit")
- elif self.path in (node.path for node in self.commit.added):
- return NodeState.ADDED
- elif self.path in (node.path for node in self.commit.changed):
- return NodeState.CHANGED
- else:
- return NodeState.NOT_CHANGED
+ return self.commit.get_file_annotate(self.bytes_path, pre_load=pre_load)
@LazyProperty
def is_binary(self):
@@ -629,7 +526,7 @@ class FileNode(Node):
Returns True if file has binary content.
"""
if self.commit:
- return self.commit.is_node_binary(self.path)
+ return self.commit.is_node_binary(self.bytes_path)
else:
raw_bytes = self._content
return bool(raw_bytes and BIN_BYTE_MARKER in raw_bytes)
@@ -641,7 +538,7 @@ class FileNode(Node):
"""
if self.commit:
- return self.commit.node_md5_hash(self.path)
+ return self.commit.node_md5_hash(self.bytes_path)
else:
raw_bytes = self._content
# TODO: this sucks, we're computing md5 on potentially super big stream data...
@@ -650,7 +547,7 @@ class FileNode(Node):
@LazyProperty
def extension(self):
"""Returns filenode extension"""
- return self.name.split('.')[-1]
+ return self.name.split(".")[-1]
@property
def is_executable(self):
@@ -667,15 +564,15 @@ class FileNode(Node):
LF store.
"""
if self.commit:
- return self.commit.get_largefile_node(self.path)
+ return self.commit.get_largefile_node(self.bytes_path)
def count_lines(self, content: str | bytes, count_empty=False):
if isinstance(content, str):
- newline_marker = '\n'
+ newline_marker = "\n"
elif isinstance(content, bytes):
- newline_marker = b'\n'
+ newline_marker = b"\n"
else:
- raise ValueError('content must be bytes or str got {type(content)} instead')
+ raise ValueError("content must be bytes or str got {type(content)} instead")
if count_empty:
all_lines = 0
@@ -704,33 +601,6 @@ class FileNode(Node):
return all_lines, empty_lines
-class RemovedFileNode(FileNode):
- """
- Dummy FileNode class - trying to access any public attribute except path,
- name, kind or state (or methods/attributes checking those two) would raise
- RemovedFileNodeError.
- """
- ALLOWED_ATTRIBUTES = [
- 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind',
- 'added', 'changed', 'not_changed', 'removed', 'bytes_path'
- ]
-
- def __init__(self, path):
- """
- :param path: relative path to the node
- """
- super().__init__(path=path)
-
- def __getattribute__(self, attr):
- if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES:
- return super().__getattribute__(attr)
- raise RemovedFileNodeError(f"Cannot access attribute {attr} on RemovedFileNode. Not in allowed attributes")
-
- @LazyProperty
- def state(self):
- return NodeState.REMOVED
-
-
class DirNode(Node):
"""
DirNode stores list of files and directories within this node.
@@ -752,7 +622,7 @@ class DirNode(Node):
super().__init__(path, NodeKind.DIR)
self.commit = commit
self._nodes = nodes
- self.default_pre_load = default_pre_load or ['is_binary', 'size']
+ self.default_pre_load = default_pre_load or ["is_binary", "size"]
def __iter__(self):
yield from self.nodes
@@ -782,10 +652,9 @@ class DirNode(Node):
@LazyProperty
def nodes(self):
if self.commit:
- nodes = self.commit.get_nodes(self.path, pre_load=self.default_pre_load)
+ nodes = self.commit.get_nodes(self.bytes_path, pre_load=self.default_pre_load)
else:
nodes = self._nodes
- self._nodes_dict = {node.path: node for node in nodes}
return sorted(nodes)
@LazyProperty
@@ -796,47 +665,6 @@ class DirNode(Node):
def dirs(self):
return sorted(node for node in self.nodes if node.is_dir())
- def get_node(self, path):
- """
- Returns node from within this particular ``DirNode``, so it is now
- allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
- 'docs'. In order to access deeper nodes one must fetch nodes between
- them first - this would work::
-
- docs = root.get_node('docs')
- docs.get_node('api').get_node('index.rst')
-
- :param: path - relative to the current node
-
- .. note::
- To access lazily (as in example above) node have to be initialized
- with related commit object - without it node is out of
- context and may know nothing about anything else than nearest
- (located at same level) nodes.
- """
- try:
- path = path.rstrip('/')
- if path == '':
- raise NodeError("Cannot retrieve node without path")
- self.nodes # access nodes first in order to set _nodes_dict
- paths = path.split('/')
- if len(paths) == 1:
- if not self.is_root():
- path = '/'.join((self.path, paths[0]))
- else:
- path = paths[0]
- return self._nodes_dict[path]
- elif len(paths) > 1:
- if self.commit is None:
- raise NodeError("Cannot access deeper nodes without commit")
- else:
- path1, path2 = paths[0], '/'.join(paths[1:])
- return self.get_node(path1).get_node(path2)
- else:
- raise KeyError
- except KeyError:
- raise NodeError(f"Node does not exist at {path}")
-
@LazyProperty
def state(self):
raise NodeError("Cannot access state of DirNode")
@@ -844,7 +672,7 @@ class DirNode(Node):
@LazyProperty
def size(self):
size = 0
- for root, dirs, files in self.commit.walk(self.path):
+ for root, dirs, files in self.commit.walk(self.bytes_path):
for f in files:
size += f.size
@@ -854,14 +682,12 @@ class DirNode(Node):
def last_commit(self):
if self.commit:
pre_load = ["author", "date", "message", "parents"]
- return self.commit.get_path_commit(self.path, pre_load=pre_load)
- raise NodeError(
- "Cannot retrieve last commit of the file without "
- "related commit attribute")
+ return self.commit.get_path_commit(self.bytes_path, pre_load=pre_load)
+ raise NodeError("Cannot retrieve last commit of the file without related commit attribute")
def __repr__(self):
- short_id = getattr(self.commit, 'short_id', '')
- return f'<{self.__class__.__name__} {self.path!r} @ {short_id}>'
+ short_id = getattr(self.commit, "short_id", "")
+ return f"<{self.__class__.__name__} path={self.str_path!r}, short_id={short_id}>"
class RootNode(DirNode):
@@ -870,37 +696,39 @@ class RootNode(DirNode):
"""
def __init__(self, nodes=(), commit=None):
- super().__init__(path=b'', nodes=nodes, commit=commit)
+ super().__init__(path=b"", nodes=nodes, commit=commit)
def __repr__(self):
- return f'<{self.__class__.__name__}>'
+ short_id = getattr(self.commit, "short_id", "")
+ return f"<{self.__class__.__name__} path={self.str_path!r}, short_id={short_id}>"
class SubModuleNode(Node):
"""
represents a SubModule of Git or SubRepo of Mercurial
"""
+
is_binary = False
size = 0
def __init__(self, name, url=None, commit=None, alias=None):
- self.path = name
+ self.path: bytes = name
+ self.str_path: str = safe_str(self.path) # we store paths as str
self.kind = NodeKind.SUBMODULE
self.alias = alias
# we have to use EmptyCommit here since this can point to svn/git/hg
# submodules we cannot get from repository
- self.commit = EmptyCommit(str(commit), alias=alias)
- self.url = url or self._extract_submodule_url()
+ self.commit = EmptyCommit(safe_str(commit), alias=alias)
+ self.url = safe_str(url) or self._extract_submodule_url()
def __repr__(self):
- short_id = getattr(self.commit, 'short_id', '')
- return f'<{self.__class__.__name__} {self.path!r} @ {short_id}>'
+ short_id = getattr(self.commit, "short_id", "")
+ return f"<{self.__class__.__name__} {self.str_path!r} @ {short_id}>"
def _extract_submodule_url(self):
- # TODO: find a way to parse gits submodule file and extract the
- # linking URL
- return self.path
+ # TODO: find a way to parse gits submodule file and extract the linking URL
+ return safe_str(self.path)
@LazyProperty
def name(self):
@@ -908,22 +736,22 @@ class SubModuleNode(Node):
Returns name of the node so if its path
then only last part is returned.
"""
- org = safe_str(self.path.rstrip('/').split('/')[-1])
- return f'{org} @ {self.commit.short_id}'
+ org = self.str_path.rstrip("/").split("/")[-1]
+ return f"{org} @ {self.commit.short_id}"
class LargeFileNode(FileNode):
-
def __init__(self, path, url=None, commit=None, alias=None, org_path=None):
self._validate_path(path) # can throw exception if path is invalid
self.org_path = org_path # as stored in VCS as LF pointer
- self.bytes_path = path.rstrip(b'/') # store for __repr__
- self.path = safe_str(self.bytes_path) # we store paths as str
+ self.bytes_path = path.rstrip(b"/") # store for __repr__
+ self.str_path = safe_str(self.bytes_path)
+ self.path = self.str_path
- self.kind = NodeKind.LARGEFILE
+ self.kind = NodeKind.LARGE_FILE
self.alias = alias
- self._content = b''
+ self._content = b""
def _validate_path(self, path: bytes):
"""
@@ -932,7 +760,7 @@ class LargeFileNode(FileNode):
self._assert_bytes(path)
def __repr__(self):
- return f'<{self.__class__.__name__} {self.org_path} -> {self.path!r}>'
+ return f"<{self.__class__.__name__} {self.org_path} -> {self.str_path!r}>"
@LazyProperty
def size(self):
@@ -940,7 +768,7 @@ class LargeFileNode(FileNode):
@LazyProperty
def raw_bytes(self):
- with open(self.path, 'rb') as f:
+ with open(self.path, "rb") as f:
content = f.read()
return content
@@ -952,7 +780,7 @@ class LargeFileNode(FileNode):
return self.org_path
def stream_bytes(self):
- with open(self.path, 'rb') as stream:
+ with open(self.path, "rb") as stream:
while True:
data = stream.read(16 * 1024)
if not data:
diff --git a/rhodecode/lib/vcs/path.py b/rhodecode/lib/vcs/path.py
--- a/rhodecode/lib/vcs/path.py
+++ b/rhodecode/lib/vcs/path.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/utils/__init__.py b/rhodecode/lib/vcs/utils/__init__.py
--- a/rhodecode/lib/vcs/utils/__init__.py
+++ b/rhodecode/lib/vcs/utils/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/utils/helpers.py b/rhodecode/lib/vcs/utils/helpers.py
--- a/rhodecode/lib/vcs/utils/helpers.py
+++ b/rhodecode/lib/vcs/utils/helpers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2023 RhodeCode GmbH
+# Copyright (C) 2014-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/utils/imports.py b/rhodecode/lib/vcs/utils/imports.py
--- a/rhodecode/lib/vcs/utils/imports.py
+++ b/rhodecode/lib/vcs/utils/imports.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs/utils/paths.py b/rhodecode/lib/vcs/utils/paths.py
--- a/rhodecode/lib/vcs/utils/paths.py
+++ b/rhodecode/lib/vcs/utils/paths.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/lib/vcs_common.py b/rhodecode/lib/vcs_common.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/vcs_common.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2014-2024 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# 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 Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+
+"""
+Common VCS module for rhodecode and vcsserver
+"""
+
+
+import enum
+
+FILEMODE_DEFAULT = 0o100644
+FILEMODE_EXECUTABLE = 0o100755
+FILEMODE_LINK = 0o120000
+
+
+class NodeKind(int, enum.Enum):
+ SUBMODULE = -1
+ DIR = 1
+ FILE = 2
+ LARGE_FILE = 3
+
+
+def map_git_obj_type(obj_type):
+ if obj_type == "blob":
+ return NodeKind.FILE
+ elif obj_type == "tree":
+ return NodeKind.DIR
+ elif obj_type == "link":
+ return NodeKind.SUBMODULE
+ return None
diff --git a/rhodecode/lib/view_utils.py b/rhodecode/lib/view_utils.py
--- a/rhodecode/lib/view_utils.py
+++ b/rhodecode/lib/view_utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/__init__.py b/rhodecode/model/__init__.py
--- a/rhodecode/model/__init__.py
+++ b/rhodecode/model/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/auth_token.py b/rhodecode/model/auth_token.py
--- a/rhodecode/model/auth_token.py
+++ b/rhodecode/model/auth_token.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 RhodeCode GmbH
+# Copyright (C) 2013-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/changeset_status.py b/rhodecode/model/changeset_status.py
--- a/rhodecode/model/changeset_status.py
+++ b/rhodecode/model/changeset_status.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py
--- a/rhodecode/model/comment.py
+++ b/rhodecode/model/comment.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -25,12 +25,13 @@ import logging
import traceback
import collections
-from pyramid.threadlocal import get_current_registry, get_current_request
+from pyramid.threadlocal import get_current_registry
from sqlalchemy.sql.expression import null
from sqlalchemy.sql.functions import coalesce
from rhodecode.lib import helpers as h, diffs, channelstream, hooks_utils
from rhodecode.lib import audit_logger
+from rhodecode.lib.pyramid_utils import get_current_request
from rhodecode.lib.exceptions import CommentVersionMismatch
from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int
from rhodecode.model import BaseModel
@@ -373,6 +374,7 @@ class CommentsModel(BaseModel):
Session().add(comment)
Session().flush()
+
kwargs = {
'user': user,
'renderer_type': renderer,
@@ -387,8 +389,7 @@ class CommentsModel(BaseModel):
}
if commit_obj:
- recipients = ChangesetComment.get_users(
- revision=commit_obj.raw_id)
+ recipients = ChangesetComment.get_users(revision=commit_obj.raw_id)
# add commit author if it's in RhodeCode system
cs_author = User.get_from_cs_author(commit_obj.author)
if not cs_author:
@@ -397,16 +398,13 @@ class CommentsModel(BaseModel):
recipients += [cs_author]
commit_comment_url = self.get_url(comment, request=request)
- commit_comment_reply_url = self.get_url(
- comment, request=request,
- anchor=f'comment-{comment.comment_id}/?/ReplyToComment')
+ commit_comment_reply_url = self.get_url(comment, request=request, anchor=f'comment-{comment.comment_id}/?/ReplyToComment')
target_repo_url = h.link_to(
repo.repo_name,
h.route_url('repo_summary', repo_name=repo.repo_name))
- commit_url = h.route_url('repo_commit', repo_name=repo.repo_name,
- commit_id=commit_id)
+ commit_url = h.route_url('repo_commit', repo_name=repo.repo_name, commit_id=commit_id)
# commit specifics
kwargs.update({
@@ -489,7 +487,6 @@ class CommentsModel(BaseModel):
if not is_draft:
comment_data = comment.get_api_data()
-
self._log_audit_action(
action, {'data': comment_data}, auth_user, comment)
diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py
--- a/rhodecode/model/db.py
+++ b/rhodecode/model/db.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -35,7 +35,7 @@ import collections
import pyotp
from sqlalchemy import (
- or_, and_, not_, func, cast, TypeDecorator, event, select,
+ or_, and_, not_, func, cast, TypeDecorator, event, select, delete,
true, false, null, union_all,
Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
@@ -274,6 +274,22 @@ class BaseModel(object):
return stmt
@classmethod
+ def delete(cls, custom_cls=None):
+ """
+ stmt = cls.delete().where(cls.user_id==1)
+ # optionally
+ stmt = cls.delete(User).where(cls.user_id==1)
+ result = cls.execute(stmt)
+ """
+
+ if custom_cls:
+ stmt = delete(custom_cls)
+ else:
+ stmt = delete(cls)
+ return stmt
+
+
+ @classmethod
def execute(cls, stmt):
return Session().execute(stmt)
@@ -284,7 +300,7 @@ class BaseModel(object):
@classmethod
def get(cls, id_):
if id_:
- return cls.query().get(id_)
+ return Session().get(cls, id_)
@classmethod
def get_or_404(cls, id_):
@@ -1075,28 +1091,26 @@ class User(Base, BaseModel):
@classmethod
def get(cls, user_id, cache=False):
if not user_id:
- return
-
- user = cls.query()
+ return None
+
+ q = cls.select().where(cls.user_id == user_id)
if cache:
- user = user.options(
- FromCache("sql_cache_short", f"get_users_{user_id}"))
- return user.get(user_id)
+ q = q.options(FromCache("sql_cache_short", f"get_users_{user_id}"))
+ return cls.execute(q).scalar_one_or_none()
@classmethod
- def get_by_username(cls, username, case_insensitive=False,
- cache=False):
+ def get_by_username(cls, username, case_insensitive=False, cache=False):
+ if not username:
+ return None
if case_insensitive:
- q = cls.select().where(
- func.lower(cls.username) == func.lower(username))
+ q = cls.select().where(func.lower(cls.username) == func.lower(username))
else:
q = cls.select().where(cls.username == username)
if cache:
hash_key = _hash_key(username)
- q = q.options(
- FromCache("sql_cache_short", f"get_user_by_name_{hash_key}"))
+ q = q.options(FromCache("sql_cache_short", f"get_user_by_name_{hash_key}"))
return cls.execute(q).scalar_one_or_none()
@@ -1125,6 +1139,8 @@ class User(Base, BaseModel):
@classmethod
def get_by_email(cls, email, case_insensitive=False, cache=False):
+ if not email:
+ return None
if case_insensitive:
q = cls.select().where(func.lower(cls.email) == func.lower(email))
@@ -1133,8 +1149,7 @@ class User(Base, BaseModel):
if cache:
email_key = _hash_key(email)
- q = q.options(
- FromCache("sql_cache_short", f"get_email_key_{email_key}"))
+ q = q.options(FromCache("sql_cache_short", f"get_email_key_{email_key}"))
ret = cls.execute(q).scalar_one_or_none()
@@ -1147,8 +1162,8 @@ class User(Base, BaseModel):
q = q.where(UserEmailMap.email == email)
q = q.options(joinedload(UserEmailMap.user))
if cache:
- q = q.options(
- FromCache("sql_cache_short", f"get_email_map_key_{email_key}"))
+ email_key = _hash_key(email)
+ q = q.options(FromCache("sql_cache_short", f"get_email_map_key_{email_key}"))
result = cls.execute(q).scalar_one_or_none()
ret = getattr(result, 'user', None)
@@ -1642,11 +1657,12 @@ class UserGroup(Base, BaseModel):
return f"<{self.cls_name}('id:{self.users_group_id}:{self.users_group_name}')>"
@classmethod
- def get_by_group_name(cls, group_name, cache=False,
- case_insensitive=False):
+ def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+ if not group_name:
+ return None
+
if case_insensitive:
- q = cls.query().filter(func.lower(cls.users_group_name) ==
- func.lower(group_name))
+ q = cls.query().filter(func.lower(cls.users_group_name) == func.lower(group_name))
else:
q = cls.query().filter(cls.users_group_name == group_name)
@@ -2968,9 +2984,11 @@ class RepoGroup(Base, BaseModel):
@classmethod
def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+ if not group_name:
+ return None
+
if case_insensitive:
- gr = cls.query().filter(func.lower(cls.group_name)
- == func.lower(group_name))
+ gr = cls.query().filter(func.lower(cls.group_name) == func.lower(group_name))
else:
gr = cls.query().filter(cls.group_name == group_name)
if cache:
diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py
--- a/rhodecode/model/forms.py
+++ b/rhodecode/model/forms.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -445,6 +445,7 @@ class _BaseVcsSettingsForm(formencode.Sc
# PR/Code-review
rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
+ rhodecode_auto_merge_enabled = v.StringBoolean(if_missing=False)
rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
# hg
diff --git a/rhodecode/model/gist.py b/rhodecode/model/gist.py
--- a/rhodecode/model/gist.py
+++ b/rhodecode/model/gist.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 RhodeCode GmbH
+# Copyright (C) 2013-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -103,7 +103,7 @@ class GistModel(BaseModel):
raise VCSError(f'Failed to load gist repository for {repo}')
commit = vcs_repo.get_commit(commit_id=revision)
- return commit, [n for n in commit.get_node('/')]
+ return commit, [n for n in commit.get_node(b'/')]
def create(self, description, owner, gist_mapping,
gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None,
diff --git a/rhodecode/model/integration.py b/rhodecode/model/integration.py
--- a/rhodecode/model/integration.py
+++ b/rhodecode/model/integration.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/meta.py b/rhodecode/model/meta.py
--- a/rhodecode/model/meta.py
+++ b/rhodecode/model/meta.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py
--- a/rhodecode/model/notification.py
+++ b/rhodecode/model/notification.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -25,11 +25,11 @@ import logging
import traceback
import premailer
-from pyramid.threadlocal import get_current_request
from sqlalchemy.sql.expression import false, true
import rhodecode
from rhodecode.lib import helpers as h
+from rhodecode.lib.pyramid_utils import get_current_request
from rhodecode.model import BaseModel
from rhodecode.model.db import Notification, User, UserNotification
from rhodecode.model.meta import Session
diff --git a/rhodecode/model/permission.py b/rhodecode/model/permission.py
--- a/rhodecode/model/permission.py
+++ b/rhodecode/model/permission.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py
--- a/rhodecode/model/pull_request.py
+++ b/rhodecode/model/pull_request.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -38,7 +38,7 @@ from rhodecode.translation import lazy_u
from rhodecode.lib import helpers as h, hooks_utils, diffs
from rhodecode.lib import audit_logger
from collections import OrderedDict
-from rhodecode.lib.hook_daemon.base import prepare_callback_daemon
+from rhodecode.lib.hook_daemon.utils import prepare_callback_daemon
from rhodecode.lib.ext_json import sjson as json
from rhodecode.lib.markup_renderer import (
DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
@@ -178,6 +178,7 @@ def get_diff_info(
log.debug('Calculating authors of changed files')
target_commit = source_repo.get_commit(ancestor_id)
+ # TODO: change to operate in bytes..
for fname, lines in changed_lines.items():
try:
@@ -980,9 +981,7 @@ class PullRequestModel(BaseModel):
target_ref = self._refresh_reference(
pull_request.target_ref_parts, target_vcs)
- callback_daemon, extras = prepare_callback_daemon(
- extras, protocol=vcs_settings.HOOKS_PROTOCOL,
- host=vcs_settings.HOOKS_HOST)
+ callback_daemon, extras = prepare_callback_daemon(extras, protocol=vcs_settings.HOOKS_PROTOCOL)
with callback_daemon:
# TODO: johbo: Implement a clean way to run a config_override
@@ -2125,6 +2124,10 @@ class PullRequestModel(BaseModel):
return self._get_general_setting(
pull_request, 'rhodecode_pr_merge_enabled')
+ def is_automatic_merge_enabled(self, pull_request):
+ return self._get_general_setting(
+ pull_request, 'rhodecode_auto_merge_enabled')
+
def _use_rebase_for_merging(self, pull_request):
repo_type = pull_request.target_repo.repo_type
if repo_type == 'hg':
@@ -2225,8 +2228,7 @@ class MergeCheck(object):
)
@classmethod
- def validate(cls, pull_request, auth_user, translator, fail_early=False,
- force_shadow_repo_refresh=False):
+ def validate(cls, pull_request, auth_user, translator, fail_early=False, force_shadow_repo_refresh=False):
_ = translator
merge_check = cls()
@@ -2287,12 +2289,10 @@ class MergeCheck(object):
# left over TODOs
todos = CommentsModel().get_pull_request_unresolved_todos(pull_request)
if todos:
- log.debug("MergeCheck: cannot merge, {} "
- "unresolved TODOs left.".format(len(todos)))
+ log.debug("MergeCheck: cannot merge, %s unresolved TODOs left.", len(todos))
if len(todos) == 1:
- msg = _('Cannot merge, {} TODO still not resolved.').format(
- len(todos))
+ msg = _('Cannot merge, {} TODO still not resolved.').format(len(todos))
else:
msg = _('Cannot merge, {} TODOs still not resolved.').format(
len(todos))
diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py
--- a/rhodecode/model/repo.py
+++ b/rhodecode/model/repo.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -33,6 +33,7 @@ from rhodecode.lib.auth import HasUserGr
from rhodecode.lib.caching_query import FromCache
from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError, AttachedArtifactsError
from rhodecode.lib import hooks_base
+from rhodecode.lib.str_utils import safe_bytes
from rhodecode.lib.user_log_filter import user_log_filter
from rhodecode.lib.utils import make_db_config
from rhodecode.lib.utils2 import (
@@ -745,7 +746,7 @@ class RepoModel(BaseModel):
log.error(traceback.format_exc())
raise
- def delete(self, repo, forks=None, pull_requests=None, artifacts=None, fs_remove=True, cur_user=None):
+ def delete(self, repo, forks=None, pull_requests=None, artifacts=None, fs_remove=True, cur_user=None, call_events=True):
"""
Delete given repository, forks parameter defines what do do with
attached forks. Throws AttachedForksError if deleted repo has attached
@@ -760,47 +761,54 @@ class RepoModel(BaseModel):
if not cur_user:
cur_user = getattr(get_current_rhodecode_user(), 'username', None)
repo = self._get_repo(repo)
- if repo:
- if forks == 'detach':
- for r in repo.forks:
- r.fork = None
- self.sa.add(r)
- elif forks == 'delete':
- for r in repo.forks:
- self.delete(r, forks='delete')
- elif [f for f in repo.forks]:
- raise AttachedForksError()
+ if not repo:
+ return False
- # check for pull requests
- pr_sources = repo.pull_requests_source
- pr_targets = repo.pull_requests_target
- if pull_requests != 'delete' and (pr_sources or pr_targets):
- raise AttachedPullRequestsError()
+ if forks == 'detach':
+ for r in repo.forks:
+ r.fork = None
+ self.sa.add(r)
+ elif forks == 'delete':
+ for r in repo.forks:
+ self.delete(r, forks='delete')
+ elif [f for f in repo.forks]:
+ raise AttachedForksError()
+
+ # check for pull requests
+ pr_sources = repo.pull_requests_source
+ pr_targets = repo.pull_requests_target
+ if pull_requests != 'delete' and (pr_sources or pr_targets):
+ raise AttachedPullRequestsError()
- artifacts_objs = repo.artifacts
- if artifacts == 'delete':
- for a in artifacts_objs:
- self.sa.delete(a)
- elif [a for a in artifacts_objs]:
- raise AttachedArtifactsError()
+ artifacts_objs = repo.artifacts
+ if artifacts == 'delete':
+ for a in artifacts_objs:
+ self.sa.delete(a)
+ elif [a for a in artifacts_objs]:
+ raise AttachedArtifactsError()
- old_repo_dict = repo.get_dict()
+ old_repo_dict = repo.get_dict()
+ if call_events:
events.trigger(events.RepoPreDeleteEvent(repo))
- try:
- self.sa.delete(repo)
- if fs_remove:
- self._delete_filesystem_repo(repo)
- else:
- log.debug('skipping removal from filesystem')
- old_repo_dict.update({
- 'deleted_by': cur_user,
- 'deleted_on': time.time(),
- })
+
+ try:
+ self.sa.delete(repo)
+ if fs_remove:
+ self._delete_filesystem_repo(repo)
+ else:
+ log.debug('skipping removal from filesystem')
+ old_repo_dict.update({
+ 'deleted_by': cur_user,
+ 'deleted_on': time.time(),
+ })
+ if call_events:
hooks_base.delete_repository(**old_repo_dict)
events.trigger(events.RepoDeleteEvent(repo))
- except Exception:
- log.error(traceback.format_exc())
- raise
+ except Exception:
+ log.error(traceback.format_exc())
+ raise
+
+ return True
def grant_user_permission(self, repo, user, perm):
"""
@@ -1102,47 +1110,49 @@ class ReadmeFinder:
different.
"""
- readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
- path_re = re.compile(r'^docs?', re.IGNORECASE)
+ readme_re = re.compile(br'^readme(\.[^.]+)?$', re.IGNORECASE)
+ path_re = re.compile(br'^docs?', re.IGNORECASE)
default_priorities = {
None: 0,
- '.rst': 1,
- '.md': 1,
- '.rest': 2,
- '.mkdn': 2,
- '.text': 2,
- '.txt': 3,
- '.mdown': 3,
- '.markdown': 4,
+ b'.rst': 1,
+ b'.md': 1,
+ b'.rest': 2,
+ b'.mkdn': 2,
+ b'.text': 2,
+ b'.txt': 3,
+ b'.mdown': 3,
+ b'.markdown': 4,
}
path_priority = {
- 'doc': 0,
- 'docs': 1,
+ b'doc': 0,
+ b'docs': 1,
}
FALLBACK_PRIORITY = 99
RENDERER_TO_EXTENSION = {
- 'rst': ['.rst', '.rest'],
- 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
+ 'rst': [b'.rst', b'.rest'],
+ 'markdown': [b'.md', b'mkdn', b'.mdown', b'.markdown'],
}
def __init__(self, default_renderer=None):
self._default_renderer = default_renderer
- self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
- default_renderer, [])
+ self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(default_renderer, [])
- def search(self, commit, path='/'):
+ def search(self, commit, path=b'/', nodes=None):
"""
Find a readme in the given `commit`.
"""
# firstly, check the PATH type if it is actually a DIR
- if commit.get_node(path).kind != NodeKind.DIR:
+ bytes_path = safe_bytes(path)
+ if commit.get_node(bytes_path).kind != NodeKind.DIR:
return None
- nodes = commit.get_nodes(path)
+ if not nodes:
+ nodes = commit.get_nodes(bytes_path)
+
matches = self._match_readmes(nodes)
matches = self._sort_according_to_priority(matches)
if matches:
@@ -1150,8 +1160,8 @@ class ReadmeFinder:
paths = self._match_paths(nodes)
paths = self._sort_paths_according_to_priority(paths)
- for path in paths:
- match = self.search(commit, path=path)
+ for bytes_path in paths:
+ match = self.search(commit, path=bytes_path)
if match:
return match
@@ -1161,7 +1171,7 @@ class ReadmeFinder:
for node in nodes:
if not node.is_file():
continue
- path = node.path.rsplit('/', 1)[-1]
+ path = node.bytes_path.rsplit(b'/', 1)[-1]
match = self.readme_re.match(path)
if match:
extension = match.group(1)
@@ -1171,28 +1181,26 @@ class ReadmeFinder:
for node in nodes:
if not node.is_dir():
continue
- match = self.path_re.match(node.path)
+ match = self.path_re.match(node.bytes_path)
if match:
- yield node.path
+ yield node.bytes_path
def _priority(self, extension):
- renderer_priority = (
- 0 if extension in self._renderer_extensions else 1)
- extension_priority = self.default_priorities.get(
- extension, self.FALLBACK_PRIORITY)
- return (renderer_priority, extension_priority)
+ renderer_priority = 0 if extension in self._renderer_extensions else 1
+ extension_priority = self.default_priorities.get(extension, self.FALLBACK_PRIORITY)
+ return renderer_priority, extension_priority
def _sort_according_to_priority(self, matches):
def priority_and_path(match):
- return (match.priority, match.path)
+ return match.priority, match.path
return sorted(matches, key=priority_and_path)
def _sort_paths_according_to_priority(self, paths):
def priority_and_path(path):
- return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
+ return self.path_priority.get(path, self.FALLBACK_PRIORITY), path
return sorted(paths, key=priority_and_path)
diff --git a/rhodecode/model/repo_group.py b/rhodecode/model/repo_group.py
--- a/rhodecode/model/repo_group.py
+++ b/rhodecode/model/repo_group.py
@@ -156,7 +156,7 @@ class RepoGroupModel(BaseModel):
def check_exist_filesystem(self, group_name, exc_on_failure=True):
create_path = os.path.join(self.repos_path, group_name)
- log.debug('creating new group in %s', create_path)
+ log.debug('checking FS presence for repo group in %s', create_path)
if os.path.isdir(create_path):
if exc_on_failure:
@@ -573,10 +573,11 @@ class RepoGroupModel(BaseModel):
log.error(traceback.format_exc())
raise
- def delete(self, repo_group, force_delete=False, fs_remove=True):
+ def delete(self, repo_group, force_delete=False, fs_remove=True, call_events=True):
repo_group = self._get_repo_group(repo_group)
if not repo_group:
return False
+ repo_group_name = repo_group.group_name
try:
self.sa.delete(repo_group)
if fs_remove:
@@ -585,13 +586,15 @@ class RepoGroupModel(BaseModel):
log.debug('skipping removal from filesystem')
# Trigger delete event.
- events.trigger(events.RepoGroupDeleteEvent(repo_group))
- return True
+ if call_events:
+ events.trigger(events.RepoGroupDeleteEvent(repo_group))
except Exception:
- log.error('Error removing repo_group %s', repo_group)
+ log.error('Error removing repo_group %s', repo_group_name)
raise
+ return True
+
def grant_user_permission(self, repo_group, user, perm):
"""
Grant permission for user on given repository group, or update
diff --git a/rhodecode/model/repo_permission.py b/rhodecode/model/repo_permission.py
--- a/rhodecode/model/repo_permission.py
+++ b/rhodecode/model/repo_permission.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py
--- a/rhodecode/model/scm.py
+++ b/rhodecode/model/scm.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -213,12 +213,10 @@ class ScmModel(BaseModel):
try:
if name in repos:
- raise RepositoryError('Duplicate repository name %s '
- 'found in %s' % (name, path))
+ raise RepositoryError(f'Duplicate repository name {name} found in {path}')
elif path[0] in rhodecode.BACKENDS:
backend = get_backend(path[0])
- repos[name] = backend(path[1], config=config,
- with_wire={"cache": False})
+ repos[name] = backend(path[1], config=config, with_wire={"cache": False})
except OSError:
continue
except RepositoryError:
@@ -545,7 +543,7 @@ class ScmModel(BaseModel):
root_path = root_path.lstrip('/')
# get RootNode, inject pre-load options before walking
- top_node = commit.get_node(root_path)
+ top_node = commit.get_node(safe_bytes(root_path))
extended_info_pre_load = []
if extended_info:
extended_info_pre_load += ['md5']
@@ -616,12 +614,13 @@ class ScmModel(BaseModel):
_files = list()
_dirs = list()
+ bytes_path = safe_bytes(root_path)
try:
_repo = self._get_repo(repo_name)
commit = _repo.scm_instance().get_commit(commit_id=commit_id)
- root_path = root_path.lstrip('/')
+ root_path = bytes_path.lstrip(b'/')
- top_node = commit.get_node(root_path)
+ top_node = commit.get_node(safe_bytes(root_path))
top_node.default_pre_load = []
for __, dirs, files in commit.walk(top_node):
@@ -738,7 +737,7 @@ class ScmModel(BaseModel):
_repo = self._get_repo(repo_name)
commit = _repo.scm_instance().get_commit(commit_id=commit_id)
root_path = root_path.lstrip('/')
- top_node = commit.get_node(root_path)
+ top_node = commit.get_node(safe_bytes(root_path))
top_node.default_pre_load = []
for __, dirs, files in commit.walk(top_node):
@@ -776,7 +775,7 @@ class ScmModel(BaseModel):
only for git
:param trigger_push_hook: trigger push hooks
- :returns: new committed commit
+ :returns: new commit
"""
user, scm_instance, message, commiter, author, imc = self.initialize_inmemory_vars(
user, repo, message, author)
diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py
--- a/rhodecode/model/settings.py
+++ b/rhodecode/model/settings.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -461,6 +461,7 @@ class VcsSettingsModel(object):
GENERAL_SETTINGS = (
'use_outdated_comments',
'pr_merge_enabled',
+ 'auto_merge_enabled',
'hg_use_rebase_for_merging',
'hg_close_branch_before_merging',
'git_use_rebase_for_merging',
@@ -862,27 +863,3 @@ class VcsSettingsModel(object):
raise ValueError(
f'The given data does not contain {data_key} key')
return data_keys
-
- def create_largeobjects_dirs_if_needed(self, repo_store_path):
- """
- This is subscribed to the `pyramid.events.ApplicationCreated` event. It
- does a repository scan if enabled in the settings.
- """
-
- from rhodecode.lib.vcs.backends.hg import largefiles_store
- from rhodecode.lib.vcs.backends.git import lfs_store
-
- paths = [
- largefiles_store(repo_store_path),
- lfs_store(repo_store_path)]
-
- for path in paths:
- if os.path.isdir(path):
- continue
- if os.path.isfile(path):
- continue
- # not a file nor dir, we try to create it
- try:
- os.makedirs(path)
- except Exception:
- log.warning('Failed to create largefiles dir:%s', path)
diff --git a/rhodecode/model/ssh_key.py b/rhodecode/model/ssh_key.py
--- a/rhodecode/model/ssh_key.py
+++ b/rhodecode/model/ssh_key.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 RhodeCode GmbH
+# Copyright (C) 2013-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/update.py b/rhodecode/model/update.py
--- a/rhodecode/model/update.py
+++ b/rhodecode/model/update.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2023 RhodeCode GmbH
+# Copyright (C) 2013-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py
--- a/rhodecode/model/user.py
+++ b/rhodecode/model/user.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -54,11 +54,12 @@ class UserModel(BaseModel):
cls = User
def get(self, user_id, cache=False):
- user = self.sa.query(User)
+ cls = self.cls
+ q = cls.select().where(cls.user_id == user_id)
if cache:
- user = user.options(
- FromCache("sql_cache_short", f"get_user_{user_id}"))
- return user.get(user_id)
+ q = q.options(
+ FromCache("sql_cache_short", f"get_users_{user_id}"))
+ return cls.execute(q).scalar_one_or_none()
def get_user(self, user):
return self._get_user(user)
diff --git a/rhodecode/model/user_group.py b/rhodecode/model/user_group.py
--- a/rhodecode/model/user_group.py
+++ b/rhodecode/model/user_group.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/__init__.py b/rhodecode/model/validation_schema/__init__.py
--- a/rhodecode/model/validation_schema/__init__.py
+++ b/rhodecode/model/validation_schema/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/preparers.py b/rhodecode/model/validation_schema/preparers.py
--- a/rhodecode/model/validation_schema/preparers.py
+++ b/rhodecode/model/validation_schema/preparers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/__init__.py b/rhodecode/model/validation_schema/schemas/__init__.py
--- a/rhodecode/model/validation_schema/schemas/__init__.py
+++ b/rhodecode/model/validation_schema/schemas/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/comment_schema.py b/rhodecode/model/validation_schema/schemas/comment_schema.py
--- a/rhodecode/model/validation_schema/schemas/comment_schema.py
+++ b/rhodecode/model/validation_schema/schemas/comment_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2017-2023 RhodeCode GmbH
+# Copyright (C) 2017-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/gist_schema.py b/rhodecode/model/validation_schema/schemas/gist_schema.py
--- a/rhodecode/model/validation_schema/schemas/gist_schema.py
+++ b/rhodecode/model/validation_schema/schemas/gist_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/integration_schema.py b/rhodecode/model/validation_schema/schemas/integration_schema.py
--- a/rhodecode/model/validation_schema/schemas/integration_schema.py
+++ b/rhodecode/model/validation_schema/schemas/integration_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/repo_group_schema.py b/rhodecode/model/validation_schema/schemas/repo_group_schema.py
--- a/rhodecode/model/validation_schema/schemas/repo_group_schema.py
+++ b/rhodecode/model/validation_schema/schemas/repo_group_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/repo_schema.py b/rhodecode/model/validation_schema/schemas/repo_schema.py
--- a/rhodecode/model/validation_schema/schemas/repo_schema.py
+++ b/rhodecode/model/validation_schema/schemas/repo_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/reviewer_schema.py b/rhodecode/model/validation_schema/schemas/reviewer_schema.py
--- a/rhodecode/model/validation_schema/schemas/reviewer_schema.py
+++ b/rhodecode/model/validation_schema/schemas/reviewer_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/search_schema.py b/rhodecode/model/validation_schema/schemas/search_schema.py
--- a/rhodecode/model/validation_schema/schemas/search_schema.py
+++ b/rhodecode/model/validation_schema/schemas/search_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/user_group_schema.py b/rhodecode/model/validation_schema/schemas/user_group_schema.py
--- a/rhodecode/model/validation_schema/schemas/user_group_schema.py
+++ b/rhodecode/model/validation_schema/schemas/user_group_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/schemas/user_schema.py b/rhodecode/model/validation_schema/schemas/user_schema.py
--- a/rhodecode/model/validation_schema/schemas/user_schema.py
+++ b/rhodecode/model/validation_schema/schemas/user_schema.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/types.py b/rhodecode/model/validation_schema/types.py
--- a/rhodecode/model/validation_schema/types.py
+++ b/rhodecode/model/validation_schema/types.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/utils.py b/rhodecode/model/validation_schema/utils.py
--- a/rhodecode/model/validation_schema/utils.py
+++ b/rhodecode/model/validation_schema/utils.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/validators.py b/rhodecode/model/validation_schema/validators.py
--- a/rhodecode/model/validation_schema/validators.py
+++ b/rhodecode/model/validation_schema/validators.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validation_schema/widgets.py b/rhodecode/model/validation_schema/widgets.py
--- a/rhodecode/model/validation_schema/widgets.py
+++ b/rhodecode/model/validation_schema/widgets.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011-2023 RhodeCode GmbH
+# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/model/validators.py b/rhodecode/model/validators.py
--- a/rhodecode/model/validators.py
+++ b/rhodecode/model/validators.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/public/css/legacy_code_styles.less b/rhodecode/public/css/legacy_code_styles.less
--- a/rhodecode/public/css/legacy_code_styles.less
+++ b/rhodecode/public/css/legacy_code_styles.less
@@ -287,10 +287,18 @@ div.markdown-block #message {
font-family: @text-monospace;
font-size: 11px;
.border-radius(@border-radius);
- background-color: white;
color: @grey3;
}
+div.markdown-block code,
+div.markdown-block pre {
+ background-color: transparent;
+}
+
+div.markdown-block #ws,
+div.markdown-block #message {
+ background-color: white;
+}
div.markdown-block code {
border: @border-thickness solid @grey6;
diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js
--- a/rhodecode/public/js/rhodecode/routes.js
+++ b/rhodecode/public/js/rhodecode/routes.js
@@ -41,6 +41,9 @@ function registerRCRoutes() {
pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
pyroutes.register('admin_scheduler', '/_admin/scheduler', []);
pyroutes.register('admin_scheduler_show_tasks', '/_admin/scheduler/_tasks', []);
+ pyroutes.register('admin_security', '/_admin/security', []);
+ pyroutes.register('admin_security_modify_allowed_vcs_client_versions', '/_admin/security/modify/allowed_vcs_client_versions', []);
+ pyroutes.register('admin_security_update', '/_admin/security/audit/update', []);
pyroutes.register('admin_settings', '/_admin/settings', []);
pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
@@ -62,7 +65,8 @@ function registerRCRoutes() {
pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
- pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
+ pyroutes.register('admin_settings_mapping_cleanup', '/_admin/settings/mapping/cleanup', []);
+ pyroutes.register('admin_settings_mapping_create', '/_admin/settings/mapping/create', []);
pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
@@ -86,7 +90,6 @@ function registerRCRoutes() {
pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
- pyroutes.register('admin_security_modify_allowed_vcs_client_versions', '/_admin/security/modify/allowed_vcs_client_versions', []);
pyroutes.register('apiv2', '/_admin/api', []);
pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
diff --git a/rhodecode/subscribers.py b/rhodecode/subscribers.py
--- a/rhodecode/subscribers.py
+++ b/rhodecode/subscribers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -57,6 +57,36 @@ def add_renderer_globals(event):
event['h'] = helpers
+def auto_merge_pr_if_needed(event):
+ #TODO To be re-enabled later
+ pass
+ # from rhodecode.model.db import PullRequest
+ # from rhodecode.model.pull_request import (
+ # PullRequestModel, ChangesetStatus, MergeCheck
+ # )
+ #
+ # pr_event_data = event.as_dict()['pullrequest']
+ # pull_request = PullRequest.get(pr_event_data['pull_request_id'])
+ # calculated_status = pr_event_data['status']
+ # if (calculated_status == ChangesetStatus.STATUS_APPROVED
+ # and PullRequestModel().is_automatic_merge_enabled(pull_request)):
+ # user = pull_request.author.AuthUser()
+ #
+ # merge_check = MergeCheck.validate(
+ # pull_request, user, translator=lambda x: x, fail_early=True
+ # )
+ # if merge_check.merge_possible:
+ # from rhodecode.lib.base import vcs_operation_context
+ # extras = vcs_operation_context(
+ # event.request.environ, repo_name=pull_request.target_repo.repo_name,
+ # username=user.username, action='push',
+ # scm=pull_request.target_repo.repo_type)
+ # from rc_ee.lib.celerylib.tasks import auto_merge_repo
+ # auto_merge_repo.apply_async(
+ # args=(pull_request.pull_request_id, extras), countdown=3
+ # )
+
+
def set_user_lang(event):
request = event.request
cur_user = getattr(request, 'user', None)
@@ -121,7 +151,7 @@ def scan_repositories_if_enabled(event):
from rhodecode.lib.utils import repo2db_mapper
scm = ScmModel()
repositories = scm.repo_scan(scm.repos_path)
- repo2db_mapper(repositories, remove_obsolete=False)
+ repo2db_mapper(repositories)
def write_metadata_if_needed(event):
diff --git a/rhodecode/templates/admin/auth/plugin_settings.mako b/rhodecode/templates/admin/auth/plugin_settings.mako
--- a/rhodecode/templates/admin/auth/plugin_settings.mako
+++ b/rhodecode/templates/admin/auth/plugin_settings.mako
@@ -143,4 +143,16 @@
});
+
%def>
diff --git a/rhodecode/templates/admin/repos/repo_edit_caches.mako b/rhodecode/templates/admin/repos/repo_edit_caches.mako
--- a/rhodecode/templates/admin/repos/repo_edit_caches.mako
+++ b/rhodecode/templates/admin/repos/repo_edit_caches.mako
@@ -10,7 +10,7 @@
${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
- ${h.api_call_example(method='invalidate_cache', args={"repoid": c.rhodecode_db_repo.repo_name})}
+ ${h.api_call_example(method='invalidate_cache', args={"repoid": c.rhodecode_db_repo.repo_id})}
diff --git a/rhodecode/templates/admin/settings/settings_global.mako b/rhodecode/templates/admin/settings/settings_global.mako
--- a/rhodecode/templates/admin/settings/settings_global.mako
+++ b/rhodecode/templates/admin/settings/settings_global.mako
@@ -68,7 +68,7 @@
${_('Google reCaptcha v2 site key.')}
- ${h.text('rhodecode_captcha_public_key',size=60)}
+ ${h.text('rhodecode_captcha_public_key',size=60, type="password")}
@@ -80,7 +80,7 @@
${_('Google reCaptcha v2 secret key.')}
- ${h.text('rhodecode_captcha_private_key',size=60)}
+ ${h.text('rhodecode_captcha_private_key',size=60, type="password")}
diff --git a/rhodecode/templates/admin/settings/settings_mapping.mako b/rhodecode/templates/admin/settings/settings_mapping.mako
--- a/rhodecode/templates/admin/settings/settings_mapping.mako
+++ b/rhodecode/templates/admin/settings/settings_mapping.mako
@@ -1,33 +1,45 @@
-${h.secure_form(h.route_path('admin_settings_mapping_update'), request=request)}
+
-
${_('Import New Groups or Repositories')}
+ ${_('Import new repository groups and repositories')}
-
+ ${h.secure_form(h.route_path('admin_settings_mapping_create'), request=request)}
- ${_('This function will scann all data under the current storage path location at')} ${c.storage_path}
+ ${_('This function will scan all data under the current storage path location at')} ${c.storage_path}
+ ${_('Each folder will be imported as a new repository group, and each repository found will be also imported to root level or corresponding repository group')}
- ${h.checkbox('destroy',True)}
- ${_('Destroy old data')}
-
-
${_('In case a repository or a group was deleted from the filesystem and it still exists in the database, check this option to remove obsolete data from the database.')}
-
-
${h.checkbox('invalidate',True)}
${_('Invalidate cache for all repositories')}
${_('Each cache data for repositories will be cleaned with this option selected. Use this to reload data and clear cache keys.')}
- ${h.submit('rescan',_('Rescan Filesystem'),class_="btn")}
+ ${h.submit('rescan',_('Scan filesystem'),class_="btn")}
-
+ ${h.end_form()}
-${h.end_form()}
+
+
+
${_('Cleanup removed Repository Groups or Repositories')}
+
+
+ ${h.secure_form(h.route_path('admin_settings_mapping_cleanup'), request=request)}
+
+ ${_('This function will scan all data under the current storage path location at')} ${c.storage_path}
+ ${_('Then it will remove all repository groups and repositories that are no longer present in the filesystem.')}
+
+
+
+ ${h.submit('rescan',_('Cleanup filesystem'),class_="btn btn-danger")}
+
+ ${h.end_form()}
+
+
+
diff --git a/rhodecode/templates/base/vcs_settings.mako b/rhodecode/templates/base/vcs_settings.mako
--- a/rhodecode/templates/base/vcs_settings.mako
+++ b/rhodecode/templates/base/vcs_settings.mako
@@ -210,6 +210,23 @@
${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}
+ %if c.rhodecode_edition_id != 'EE':
+
+
+ ${_('Enable automatic merge for approved pull requests')}
+
+
+
${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='sales@rhodecode.com ')|n}
+
+ %else:
+
+ ${h.checkbox('rhodecode_auto_merge_enabled' + suffix, 'True', **kwargs)}
+ ${_('Enable automatic merge for approved pull requests')}
+
+
+ ${_('When this is enabled, the pull request will be merged once it has at least one reviewer and is approved.')}
+
+ %endif
${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
${_('Invalidate and relocate inline comments during update')}
diff --git a/rhodecode/templates/changeset/changeset.mako b/rhodecode/templates/changeset/changeset.mako
--- a/rhodecode/templates/changeset/changeset.mako
+++ b/rhodecode/templates/changeset/changeset.mako
@@ -128,7 +128,7 @@
-
+
${_('Show More')}
@@ -416,6 +416,51 @@
var channel = '${c.commit_broadcast_channel}';
new ReviewerPresenceController(channel)
+ function breakLongCommitMessage() {
+ const commitElements = document.querySelectorAll(".left-content-message .commit");
+ const maxAllowedWidth = window.innerWidth * 0.9;
+
+ commitElements.forEach(commitElement => {
+ const originalText = commitElement.textContent;
+ const lines = originalText.split("\n");
+ const brokenLines = [];
+
+ commitElement.style.whiteSpace = "nowrap";
+
+ for (let line of lines) {
+ let brokenLine = "";
+ let words = line.split(" ");
+ let currentLine = "";
+
+ words.forEach(word => {
+ const testLine = currentLine.length > 0 ? currentLine + " " + word : word;
+
+ commitElement.textContent = testLine;
+ const testLineWidth = commitElement.offsetWidth;
+
+ if (testLineWidth > maxAllowedWidth) {
+ brokenLine += currentLine + "\n";
+ currentLine = word;
+ } else {
+ currentLine = testLine;
+ }
+ });
+
+ brokenLine += currentLine;
+ brokenLines.push(brokenLine.trim());
+ }
+
+ commitElement.textContent = brokenLines.join("\n");
+
+ commitElement.style.whiteSpace = "pre-wrap";
+ });
+ }
+
+ window.addEventListener("load", function () {
+ const button = document.getElementById("break-button");
+ button.addEventListener("click", breakLongCommitMessage);
+
+ });
})
diff --git a/rhodecode/templates/email_templates/base.mako b/rhodecode/templates/email_templates/base.mako
--- a/rhodecode/templates/email_templates/base.mako
+++ b/rhodecode/templates/email_templates/base.mako
@@ -409,8 +409,17 @@ text_monospace = "'Menlo', 'Liberation M
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
+ color: #7E7F7F
+ }
+
+ div.markdown-block code,
+ div.markdown-block pre {
+ background-color: transparent;
+ }
+
+ div.markdown-block #ws,
+ div.markdown-block #message {
background-color: #FFFFFF;
- color: #7E7F7F
}
div.markdown-block code {
diff --git a/rhodecode/templates/files/files_browser_tree.mako b/rhodecode/templates/files/files_browser_tree.mako
--- a/rhodecode/templates/files/files_browser_tree.mako
+++ b/rhodecode/templates/files/files_browser_tree.mako
@@ -38,7 +38,7 @@ http://docker-dev:10020/ipython/files/ma
<% has_files = False %>
% if not c.file.is_submodule():
- % for cnt, node in enumerate(c.file):
+ % for cnt, node in enumerate(c.file_nodes):
<% has_files = True %>
@@ -105,6 +105,8 @@ http://docker-dev:10020/ipython/files/ma
${h.escape(c.file.name)}
${c.file.url}
+ % else:
+ ${_('Empty directory')}
%endif
diff --git a/rhodecode/templates/pullrequests/pullrequest_show.mako b/rhodecode/templates/pullrequests/pullrequest_show.mako
--- a/rhodecode/templates/pullrequests/pullrequest_show.mako
+++ b/rhodecode/templates/pullrequests/pullrequest_show.mako
@@ -1054,6 +1054,16 @@ window.setObserversData = ${c.pull_reque
new ReviewerPresenceController(channel)
// register globally so inject comment logic can re-use it.
window.commentsController = commentsController;
+
+ // hiding status change dropdown for observers
+ const isHidden = ${'true' if c.status_change_disabled else 'false'};
+
+ if (isHidden) {
+ const element = document.getElementById("s2id_change_status_general");
+ if (element) {
+ element.style.display = "none";
+ }
+ }
})
diff --git a/rhodecode/tests/__init__.py b/rhodecode/tests/__init__.py
--- a/rhodecode/tests/__init__.py
+++ b/rhodecode/tests/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -34,49 +33,20 @@ from rhodecode.lib import helpers as h
from rhodecode.lib.helpers import flash
from rhodecode.lib.str_utils import safe_str
from rhodecode.lib.hash_utils import sha1_safe
+from rhodecode.bootstrap import \
+ TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
+ TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, TEST_USER_REGULAR_EMAIL, \
+ TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL,\
+ HG_REPO, GIT_REPO, SVN_REPO,\
+ NEW_HG_REPO, NEW_GIT_REPO,\
+ HG_FORK, GIT_FORK
log = logging.getLogger(__name__)
-__all__ = [
- 'get_new_dir', 'TestController',
- 'clear_cache_regions',
- 'assert_session_flash', 'login_user', 'no_newline_id_generator',
- 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
- 'NEW_HG_REPO', 'NEW_GIT_REPO',
- 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
- 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
- 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
- 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
- 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
- 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'SCM_TESTS',
-]
-
# SOME GLOBALS FOR TESTS
TEST_DIR = tempfile.gettempdir()
-TEST_USER_ADMIN_LOGIN = 'test_admin'
-TEST_USER_ADMIN_PASS = 'test12'
-TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
-
-TEST_USER_REGULAR_LOGIN = 'test_regular'
-TEST_USER_REGULAR_PASS = 'test12'
-TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
-
-TEST_USER_REGULAR2_LOGIN = 'test_regular2'
-TEST_USER_REGULAR2_PASS = 'test12'
-TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
-
-HG_REPO = 'vcs_test_hg'
-GIT_REPO = 'vcs_test_git'
-SVN_REPO = 'vcs_test_svn'
-
-NEW_HG_REPO = 'vcs_test_hg_new'
-NEW_GIT_REPO = 'vcs_test_git_new'
-
-HG_FORK = 'vcs_test_hg_fork'
-GIT_FORK = 'vcs_test_git_fork'
-
## VCS
SCM_TESTS = ['hg', 'git']
uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
@@ -244,3 +214,11 @@ def no_newline_id_generator(test_name):
return test_name or 'test-with-empty-name'
+def console_printer(*msg):
+ print_func = print
+ try:
+ from rich import print as print_func
+ except ImportError:
+ pass
+
+ print_func(*msg)
diff --git a/rhodecode/tests/auth_external_test.py b/rhodecode/tests/auth_external_test.py
--- a/rhodecode/tests/auth_external_test.py
+++ b/rhodecode/tests/auth_external_test.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -90,7 +89,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExter
'firstname': firstname,
'lastname': lastname,
'groups': [],
- 'email': '%s@rhodecode.com' % username,
+ 'email': f'{username}@rhodecode.com',
'admin': admin,
'active': active,
"active_from_extern": None,
diff --git a/rhodecode/tests/config/__init__.py b/rhodecode/tests/config/__init__.py
--- a/rhodecode/tests/config/__init__.py
+++ b/rhodecode/tests/config/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/config/test_environment.py b/rhodecode/tests/config/test_environment.py
--- a/rhodecode/tests/config/test_environment.py
+++ b/rhodecode/tests/config/test_environment.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/config/test_routing_links.py b/rhodecode/tests/config/test_routing_links.py
--- a/rhodecode/tests/config/test_routing_links.py
+++ b/rhodecode/tests/config/test_routing_links.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,14 +19,14 @@
import pytest
import requests
from rhodecode.config import routing_links
-
+from rhodecode.tests import console_printer
def check_connection():
try:
response = requests.get('https://rhodecode.com')
return response.status_code == 200
except Exception as e:
- print(e)
+ console_printer(e)
return False
@@ -37,8 +36,8 @@ connection_available = pytest.mark.skipi
import requests
+from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
-from requests.packages.urllib3.util.retry import Retry
def requests_retry_session(
diff --git a/rhodecode/tests/config/test_sanitize_settings.py b/rhodecode/tests/config/test_sanitize_settings.py
--- a/rhodecode/tests/config/test_sanitize_settings.py
+++ b/rhodecode/tests/config/test_sanitize_settings.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/config/test_utils.py b/rhodecode/tests/config/test_utils.py
--- a/rhodecode/tests/config/test_utils.py
+++ b/rhodecode/tests/config/test_utils.py
@@ -1,6 +1,4 @@
-
-
-# Copyright (C) 2012-2023 RhodeCode GmbH
+# Copyright (C) 2012-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/conftest.py b/rhodecode/tests/conftest.py
--- a/rhodecode/tests/conftest.py
+++ b/rhodecode/tests/conftest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -16,23 +16,10 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
-"""
-py.test config for test suite for making push/pull operations.
-
-.. important::
-
- You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
- to redirect things to stderr instead of stdout.
-"""
-
-import pytest
+import pytest # noqa
import logging
-
-from rhodecode.authentication import AuthenticationPluginRegistry
-from rhodecode.model.db import Permission, User
-from rhodecode.model.meta import Session
-from rhodecode.model.settings import SettingsModel
-from rhodecode.model.user import UserModel
+import collections
+import rhodecode
log = logging.getLogger(__name__)
@@ -40,99 +27,3 @@ log = logging.getLogger(__name__)
# Docker image running httpbin...
HTTPBIN_DOMAIN = 'http://httpbin'
HTTPBIN_POST = HTTPBIN_DOMAIN + '/post'
-
-
-@pytest.fixture()
-def enable_auth_plugins(request, baseapp, csrf_token):
- """
- Return a factory object that when called, allows to control which
- authentication plugins are enabled.
- """
-
- class AuthPluginManager(object):
-
- def cleanup(self):
- self._enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
-
- def enable(self, plugins_list, override=None):
- return self._enable_plugins(plugins_list, override)
-
- def _enable_plugins(self, plugins_list, override=None):
- override = override or {}
- params = {
- 'auth_plugins': ','.join(plugins_list),
- }
-
- # helper translate some names to others, to fix settings code
- name_map = {
- 'token': 'authtoken'
- }
- log.debug('enable_auth_plugins: enabling following auth-plugins: %s', plugins_list)
-
- for module in plugins_list:
- plugin_name = module.partition('#')[-1]
- if plugin_name in name_map:
- plugin_name = name_map[plugin_name]
- enabled_plugin = f'auth_{plugin_name}_enabled'
- cache_ttl = f'auth_{plugin_name}_cache_ttl'
-
- # default params that are needed for each plugin,
- # `enabled` and `cache_ttl`
- params.update({
- enabled_plugin: True,
- cache_ttl: 0
- })
- if override.get:
- params.update(override.get(module, {}))
-
- validated_params = params
-
- for k, v in validated_params.items():
- setting = SettingsModel().create_or_update_setting(k, v)
- Session().add(setting)
- Session().commit()
-
- AuthenticationPluginRegistry.invalidate_auth_plugins_cache(hard=True)
-
- enabled_plugins = SettingsModel().get_auth_plugins()
- assert plugins_list == enabled_plugins
-
- enabler = AuthPluginManager()
- request.addfinalizer(enabler.cleanup)
-
- return enabler
-
-
-@pytest.fixture()
-def test_user_factory(request, baseapp):
-
- def user_factory(username='test_user', password='qweqwe', first_name='John', last_name='Testing', **kwargs):
- usr = UserModel().create_or_update(
- username=username,
- password=password,
- email=f'{username}@rhodecode.org',
- firstname=first_name, lastname=last_name)
- Session().commit()
-
- for k, v in kwargs.items():
- setattr(usr, k, v)
- Session().add(usr)
-
- new_usr = User.get_by_username(username)
- new_usr_id = new_usr.user_id
- assert new_usr == usr
-
- @request.addfinalizer
- def cleanup():
- if User.get(new_usr_id) is None:
- return
-
- perm = Permission.query().all()
- for p in perm:
- UserModel().revoke_perm(usr, p)
-
- UserModel().delete(new_usr_id)
- Session().commit()
- return usr
-
- return user_factory
diff --git a/rhodecode/tests/conftest_common.py b/rhodecode/tests/conftest_common.py
--- a/rhodecode/tests/conftest_common.py
+++ b/rhodecode/tests/conftest_common.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -98,16 +98,16 @@ def pytest_addoption(parser):
'pyramid_config',
"Set up a Pyramid environment with the specified config file.")
+ parser.addini('rhodecode_config', 'rhodecode config ini for tests')
+ parser.addini('celery_config', 'celery config ini for tests')
+ parser.addini('vcsserver_config', 'vcsserver config ini for tests')
+
vcsgroup = parser.getgroup('vcs')
+
vcsgroup.addoption(
'--without-vcsserver', dest='with_vcsserver', action='store_false',
help="Do not start the VCSServer in a background process.")
- vcsgroup.addoption(
- '--with-vcsserver-http', dest='vcsserver_config_http',
- help="Start the HTTP VCSServer with the specified config file.")
- vcsgroup.addoption(
- '--vcsserver-protocol', dest='vcsserver_protocol',
- help="Start the VCSServer with HTTP protocol support.")
+
vcsgroup.addoption(
'--vcsserver-config-override', action='store', type=_parse_json,
default=None, dest='vcsserver_config_override', help=(
@@ -122,12 +122,6 @@ def pytest_addoption(parser):
"Allows to set the port of the vcsserver. Useful when testing "
"against an already running server and random ports cause "
"trouble."))
- parser.addini(
- 'vcsserver_config_http',
- "Start the HTTP VCSServer with the specified config file.")
- parser.addini(
- 'vcsserver_protocol',
- "Start the VCSServer with HTTP protocol support.")
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
diff --git a/rhodecode/tests/database/__init__.py b/rhodecode/tests/database/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/tests/database/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (C) 2010-2024 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# 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 Affero General Public License
+# along with this program. If not, see
.
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
diff --git a/rhodecode/tests/database/conftest.py b/rhodecode/tests/database/conftest.py
--- a/rhodecode/tests/database/conftest.py
+++ b/rhodecode/tests/database/conftest.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -17,7 +16,7 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
-from subprocess import Popen, PIPE
+import subprocess
import os
import sys
import tempfile
@@ -26,87 +25,71 @@ import pytest
from sqlalchemy.engine import url
from rhodecode.lib.str_utils import safe_str, safe_bytes
-from rhodecode.tests.fixture import TestINI
+from rhodecode.tests.fixtures.rc_fixture import TestINI
def _get_dbs_from_metafunc(metafunc):
- dbs_mark = metafunc.definition.get_closest_marker('dbs')
+ dbs_mark = metafunc.definition.get_closest_marker("dbs")
if dbs_mark:
# Supported backends by this test function, created from pytest.mark.dbs
backends = dbs_mark.args
else:
- backends = metafunc.config.getoption('--dbs')
+ backends = metafunc.config.getoption("--dbs")
return backends
def pytest_generate_tests(metafunc):
# Support test generation based on --dbs parameter
- if 'db_backend' in metafunc.fixturenames:
- requested_backends = set(metafunc.config.getoption('--dbs'))
+ if "db_backend" in metafunc.fixturenames:
+ requested_backends = set(metafunc.config.getoption("--dbs"))
backends = _get_dbs_from_metafunc(metafunc)
backends = requested_backends.intersection(backends)
# TODO: johbo: Disabling a backend did not work out with
# parametrization, find better way to achieve this.
if not backends:
metafunc.function._skip = True
- metafunc.parametrize('db_backend_name', backends)
+ metafunc.parametrize("db_backend_name", backends)
def pytest_collection_modifyitems(session, config, items):
- remaining = [
- i for i in items if not getattr(i.obj, '_skip', False)]
+ remaining = [i for i in items if not getattr(i.obj, "_skip", False)]
items[:] = remaining
@pytest.fixture()
-def db_backend(
- request, db_backend_name, ini_config, tmpdir_factory):
+def db_backend(request, db_backend_name, ini_config, tmpdir_factory):
basetemp = tmpdir_factory.getbasetemp().strpath
klass = _get_backend(db_backend_name)
- option_name = '--{}-connection-string'.format(db_backend_name)
+ option_name = "--{}-connection-string".format(db_backend_name)
connection_string = request.config.getoption(option_name) or None
- return klass(
- config_file=ini_config, basetemp=basetemp,
- connection_string=connection_string)
+ return klass(config_file=ini_config, basetemp=basetemp, connection_string=connection_string)
def _get_backend(backend_type):
- return {
- 'sqlite': SQLiteDBBackend,
- 'postgres': PostgresDBBackend,
- 'mysql': MySQLDBBackend,
- '': EmptyDBBackend
- }[backend_type]
+ return {"sqlite": SQLiteDBBackend, "postgres": PostgresDBBackend, "mysql": MySQLDBBackend, "": EmptyDBBackend}[
+ backend_type
+ ]
class DBBackend(object):
_store = os.path.dirname(os.path.abspath(__file__))
_type = None
- _base_ini_config = [{'app:main': {'vcs.start_server': 'false',
- 'startup.import_repos': 'false'}}]
- _db_url = [{'app:main': {'sqlalchemy.db1.url': ''}}]
- _base_db_name = 'rhodecode_test_db_backend'
- std_env = {'RC_TEST': '0'}
+ _base_ini_config = [{"app:main": {"vcs.start_server": "false", "startup.import_repos": "false"}}]
+ _db_url = [{"app:main": {"sqlalchemy.db1.url": ""}}]
+ _base_db_name = "rhodecode_test_db_backend"
+ std_env = {"RC_TEST": "0"}
- def __init__(
- self, config_file, db_name=None, basetemp=None,
- connection_string=None):
-
- from rhodecode.lib.vcs.backends.hg import largefiles_store
- from rhodecode.lib.vcs.backends.git import lfs_store
-
+ def __init__(self, config_file, db_name=None, basetemp=None, connection_string=None):
self.fixture_store = os.path.join(self._store, self._type)
self.db_name = db_name or self._base_db_name
self._base_ini_file = config_file
- self.stderr = ''
- self.stdout = ''
+ self.stderr = ""
+ self.stdout = ""
self._basetemp = basetemp or tempfile.gettempdir()
- self._repos_location = os.path.join(self._basetemp, 'rc_test_repos')
- self._repos_hg_largefiles_store = largefiles_store(self._basetemp)
- self._repos_git_lfs_store = lfs_store(self._basetemp)
+ self._repos_location = os.path.join(self._basetemp, "rc_test_repos")
self.connection_string = connection_string
@property
@@ -118,8 +101,7 @@ class DBBackend(object):
if not new_connection_string:
new_connection_string = self.get_default_connection_string()
else:
- new_connection_string = new_connection_string.format(
- db_name=self.db_name)
+ new_connection_string = new_connection_string.format(db_name=self.db_name)
url_parts = url.make_url(new_connection_string)
self._connection_string = new_connection_string
self.user = url_parts.username
@@ -127,73 +109,67 @@ class DBBackend(object):
self.host = url_parts.host
def get_default_connection_string(self):
- raise NotImplementedError('default connection_string is required.')
+ raise NotImplementedError("default connection_string is required.")
def execute(self, cmd, env=None, *args):
"""
Runs command on the system with given ``args``.
"""
- command = cmd + ' ' + ' '.join(args)
- sys.stdout.write(f'CMD: {command}')
+ command = cmd + " " + " ".join(args)
+ sys.stdout.write(f"CMD: {command}")
# Tell Python to use UTF-8 encoding out stdout
_env = os.environ.copy()
- _env['PYTHONIOENCODING'] = 'UTF-8'
+ _env["PYTHONIOENCODING"] = "UTF-8"
_env.update(self.std_env)
if env:
_env.update(env)
- self.p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, env=_env)
+ self.p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=_env)
self.stdout, self.stderr = self.p.communicate()
stdout_str = safe_str(self.stdout)
- sys.stdout.write(f'COMMAND:{command}\n')
+ sys.stdout.write(f"COMMAND:{command}\n")
sys.stdout.write(stdout_str)
return self.stdout, self.stderr
def assert_returncode_success(self):
from rich import print as pprint
+
if not self.p.returncode == 0:
pprint(safe_str(self.stderr))
- raise AssertionError(f'non 0 retcode:{self.p.returncode}')
+ raise AssertionError(f"non 0 retcode:{self.p.returncode}")
def assert_correct_output(self, stdout, version):
- assert b'UPGRADE FOR STEP %b COMPLETED' % safe_bytes(version) in stdout
+ assert b"UPGRADE FOR STEP %b COMPLETED" % safe_bytes(version) in stdout
def setup_rhodecode_db(self, ini_params=None, env=None):
if not ini_params:
ini_params = self._base_ini_config
ini_params.extend(self._db_url)
- with TestINI(self._base_ini_file, ini_params,
- self._type, destroy=True) as _ini_file:
-
+ with TestINI(self._base_ini_file, ini_params, self._type, destroy=True) as _ini_file:
if not os.path.isdir(self._repos_location):
os.makedirs(self._repos_location)
- if not os.path.isdir(self._repos_hg_largefiles_store):
- os.makedirs(self._repos_hg_largefiles_store)
- if not os.path.isdir(self._repos_git_lfs_store):
- os.makedirs(self._repos_git_lfs_store)
return self.execute(
"rc-setup-app {0} --user=marcink "
"--email=marcin@rhodeocode.com --password={1} "
- "--repos={2} --force-yes".format(
- _ini_file, 'qweqwe', self._repos_location), env=env)
+ "--repos={2} --force-yes".format(_ini_file, "qweqwe", self._repos_location),
+ env=env,
+ )
def upgrade_database(self, ini_params=None):
if not ini_params:
ini_params = self._base_ini_config
ini_params.extend(self._db_url)
- test_ini = TestINI(
- self._base_ini_file, ini_params, self._type, destroy=True)
+ test_ini = TestINI(self._base_ini_file, ini_params, self._type, destroy=True)
with test_ini as ini_file:
if not os.path.isdir(self._repos_location):
os.makedirs(self._repos_location)
- return self.execute(
- "rc-upgrade-db {0} --force-yes".format(ini_file))
+ return self.execute("rc-upgrade-db {0} --force-yes".format(ini_file))
def setup_db(self):
raise NotImplementedError
@@ -206,7 +182,7 @@ class DBBackend(object):
class EmptyDBBackend(DBBackend):
- _type = ''
+ _type = ""
def setup_db(self):
pass
@@ -222,21 +198,20 @@ class EmptyDBBackend(DBBackend):
class SQLiteDBBackend(DBBackend):
- _type = 'sqlite'
+ _type = "sqlite"
def get_default_connection_string(self):
- return 'sqlite:///{}/{}.sqlite'.format(self._basetemp, self.db_name)
+ return "sqlite:///{}/{}.sqlite".format(self._basetemp, self.db_name)
def setup_db(self):
# dump schema for tests
# cp -v $TEST_DB_NAME
- self._db_url = [{'app:main': {
- 'sqlalchemy.db1.url': self.connection_string}}]
+ self._db_url = [{"app:main": {"sqlalchemy.db1.url": self.connection_string}}]
def import_dump(self, dumpname):
dump = os.path.join(self.fixture_store, dumpname)
- target = os.path.join(self._basetemp, '{0.db_name}.sqlite'.format(self))
- return self.execute(f'cp -v {dump} {target}')
+ target = os.path.join(self._basetemp, "{0.db_name}.sqlite".format(self))
+ return self.execute(f"cp -v {dump} {target}")
def teardown_db(self):
target_db = os.path.join(self._basetemp, self.db_name)
@@ -244,39 +219,39 @@ class SQLiteDBBackend(DBBackend):
class MySQLDBBackend(DBBackend):
- _type = 'mysql'
+ _type = "mysql"
def get_default_connection_string(self):
- return 'mysql://root:qweqwe@127.0.0.1/{}'.format(self.db_name)
+ return "mysql://root:qweqwe@127.0.0.1/{}".format(self.db_name)
def setup_db(self):
# dump schema for tests
# mysqldump -uroot -pqweqwe $TEST_DB_NAME
- self._db_url = [{'app:main': {
- 'sqlalchemy.db1.url': self.connection_string}}]
- return self.execute("mysql -v -u{} -p{} -e 'create database '{}';'".format(
- self.user, self.password, self.db_name))
+ self._db_url = [{"app:main": {"sqlalchemy.db1.url": self.connection_string}}]
+ return self.execute(
+ "mysql -v -u{} -p{} -e 'create database '{}';'".format(self.user, self.password, self.db_name)
+ )
def import_dump(self, dumpname):
dump = os.path.join(self.fixture_store, dumpname)
- return self.execute("mysql -u{} -p{} {} < {}".format(
- self.user, self.password, self.db_name, dump))
+ return self.execute("mysql -u{} -p{} {} < {}".format(self.user, self.password, self.db_name, dump))
def teardown_db(self):
- return self.execute("mysql -v -u{} -p{} -e 'drop database '{}';'".format(
- self.user, self.password, self.db_name))
+ return self.execute(
+ "mysql -v -u{} -p{} -e 'drop database '{}';'".format(self.user, self.password, self.db_name)
+ )
class PostgresDBBackend(DBBackend):
- _type = 'postgres'
+ _type = "postgres"
def get_default_connection_string(self):
- return 'postgresql://postgres:qweqwe@localhost/{}'.format(self.db_name)
+ return "postgresql://postgres:qweqwe@localhost/{}".format(self.db_name)
def setup_db(self):
# dump schema for tests
# pg_dump -U postgres -h localhost $TEST_DB_NAME
- self._db_url = [{'app:main': {'sqlalchemy.db1.url': self.connection_string}}]
+ self._db_url = [{"app:main": {"sqlalchemy.db1.url": self.connection_string}}]
cmd = f"PGPASSWORD={self.password} psql -U {self.user} -h localhost -c 'create database '{self.db_name}';'"
return self.execute(cmd)
diff --git a/rhodecode/tests/database/test_creation.py b/rhodecode/tests/database/test_creation.py
--- a/rhodecode/tests/database/test_creation.py
+++ b/rhodecode/tests/database/test_creation.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/database/test_migration.py b/rhodecode/tests/database/test_migration.py
--- a/rhodecode/tests/database/test_migration.py
+++ b/rhodecode/tests/database/test_migration.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -21,33 +20,42 @@ import pytest
@pytest.mark.dbs("postgres")
-@pytest.mark.parametrize("dumpname", [
- '1.4.4.sql',
- '1.5.0.sql',
- '1.6.0.sql',
- '1.6.0_no_repo_name_index.sql',
-])
+@pytest.mark.parametrize(
+ "dumpname",
+ [
+ "1.4.4.sql",
+ "1.5.0.sql",
+ "1.6.0.sql",
+ "1.6.0_no_repo_name_index.sql",
+ ],
+)
def test_migrate_postgres_db(db_backend, dumpname):
_run_migration_test(db_backend, dumpname)
@pytest.mark.dbs("sqlite")
-@pytest.mark.parametrize("dumpname", [
- 'rhodecode.1.4.4.sqlite',
- 'rhodecode.1.4.4_with_groups.sqlite',
- 'rhodecode.1.4.4_with_ldap_active.sqlite',
-])
+@pytest.mark.parametrize(
+ "dumpname",
+ [
+ "rhodecode.1.4.4.sqlite",
+ "rhodecode.1.4.4_with_groups.sqlite",
+ "rhodecode.1.4.4_with_ldap_active.sqlite",
+ ],
+)
def test_migrate_sqlite_db(db_backend, dumpname):
_run_migration_test(db_backend, dumpname)
@pytest.mark.dbs("mysql")
-@pytest.mark.parametrize("dumpname", [
- '1.4.4.sql',
- '1.5.0.sql',
- '1.6.0.sql',
- '1.6.0_no_repo_name_index.sql',
-])
+@pytest.mark.parametrize(
+ "dumpname",
+ [
+ "1.4.4.sql",
+ "1.5.0.sql",
+ "1.6.0.sql",
+ "1.6.0_no_repo_name_index.sql",
+ ],
+)
def test_migrate_mysql_db(db_backend, dumpname):
_run_migration_test(db_backend, dumpname)
@@ -60,5 +68,5 @@ def _run_migration_test(db_backend, dump
db_backend.import_dump(dumpname)
stdout, stderr = db_backend.upgrade_database()
- db_backend.assert_correct_output(stdout+stderr, version='16')
+ db_backend.assert_correct_output(stdout + stderr, version="16")
db_backend.assert_returncode_success()
diff --git a/rhodecode/tests/events/__init__.py b/rhodecode/tests/events/__init__.py
--- a/rhodecode/tests/events/__init__.py
+++ b/rhodecode/tests/events/__init__.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/events/conftest.py b/rhodecode/tests/events/conftest.py
--- a/rhodecode/tests/events/conftest.py
+++ b/rhodecode/tests/events/conftest.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016-2023 RhodeCode GmbH
+# Copyright (C) 2016-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/events/test_pullrequest.py b/rhodecode/tests/events/test_pullrequest.py
--- a/rhodecode/tests/events/test_pullrequest.py
+++ b/rhodecode/tests/events/test_pullrequest.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/events/test_repo.py b/rhodecode/tests/events/test_repo.py
--- a/rhodecode/tests/events/test_repo.py
+++ b/rhodecode/tests/events/test_repo.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
diff --git a/rhodecode/tests/fixture_mods/__init__.py b/rhodecode/tests/fixtures/__init__.py
rename from rhodecode/tests/fixture_mods/__init__.py
rename to rhodecode/tests/fixtures/__init__.py
diff --git a/rhodecode/tests/fixtures/diff_with_diff_data.diff b/rhodecode/tests/fixtures/diff_fixtures/diff_with_diff_data.diff
rename from rhodecode/tests/fixtures/diff_with_diff_data.diff
rename to rhodecode/tests/fixtures/diff_fixtures/diff_with_diff_data.diff
diff --git a/rhodecode/tests/fixtures/git_diff_binary_and_normal.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_and_normal.diff
rename from rhodecode/tests/fixtures/git_diff_binary_and_normal.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_and_normal.diff
diff --git a/rhodecode/tests/fixtures/git_diff_binary_special_files.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files.diff
rename from rhodecode/tests/fixtures/git_diff_binary_special_files.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files.diff
diff --git a/rhodecode/tests/fixtures/git_diff_binary_special_files_2.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files_2.diff
rename from rhodecode/tests/fixtures/git_diff_binary_special_files_2.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_binary_special_files_2.diff
diff --git a/rhodecode/tests/fixtures/git_diff_chmod.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_chmod.diff
rename from rhodecode/tests/fixtures/git_diff_chmod.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_chmod.diff
diff --git a/rhodecode/tests/fixtures/git_diff_js_chars.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_js_chars.diff
rename from rhodecode/tests/fixtures/git_diff_js_chars.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_js_chars.diff
diff --git a/rhodecode/tests/fixtures/git_diff_mod_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_mod_single_binary_file.diff
rename from rhodecode/tests/fixtures/git_diff_mod_single_binary_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_mod_single_binary_file.diff
diff --git a/rhodecode/tests/fixtures/git_diff_rename_file.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file.diff
rename from rhodecode/tests/fixtures/git_diff_rename_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file.diff
diff --git a/rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff b/rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file_with_spaces.diff
rename from rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff
rename to rhodecode/tests/fixtures/diff_fixtures/git_diff_rename_file_with_spaces.diff
diff --git a/rhodecode/tests/fixtures/git_node_history_response.json b/rhodecode/tests/fixtures/diff_fixtures/git_node_history_response.json
rename from rhodecode/tests/fixtures/git_node_history_response.json
rename to rhodecode/tests/fixtures/diff_fixtures/git_node_history_response.json
diff --git a/rhodecode/tests/fixtures/hg_diff_add_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_add_single_binary_file.diff
rename from rhodecode/tests/fixtures/hg_diff_add_single_binary_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_add_single_binary_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_binary_and_normal.diff
rename from rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_binary_and_normal.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_chmod.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod.diff
rename from rhodecode/tests/fixtures/hg_diff_chmod.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod_and_mod_single_binary_file.diff
rename from rhodecode/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_chmod_and_mod_single_binary_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_copy_and_chmod_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_chmod_file.diff
rename from rhodecode/tests/fixtures/hg_diff_copy_and_chmod_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_chmod_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_copy_and_modify_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_modify_file.diff
rename from rhodecode/tests/fixtures/hg_diff_copy_and_modify_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_and_modify_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_copy_chmod_and_edit_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_chmod_and_edit_file.diff
rename from rhodecode/tests/fixtures/hg_diff_copy_chmod_and_edit_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_chmod_and_edit_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_copy_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file.diff
rename from rhodecode/tests/fixtures/hg_diff_copy_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file_with_spaces.diff
rename from rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_copy_file_with_spaces.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_del_single_binary_file.diff
rename from rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_del_single_binary_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_double_file_change_double_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_double_newline.diff
rename from rhodecode/tests/fixtures/hg_diff_double_file_change_double_newline.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_double_newline.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_double_file_change_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_newline.diff
rename from rhodecode/tests/fixtures/hg_diff_double_file_change_newline.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_double_file_change_newline.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_four_file_change_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_four_file_change_newline.diff
rename from rhodecode/tests/fixtures/hg_diff_four_file_change_newline.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_four_file_change_newline.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_mixed_filename_encodings.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mixed_filename_encodings.diff
rename from rhodecode/tests/fixtures/hg_diff_mixed_filename_encodings.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mixed_filename_encodings.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_mod_file_and_rename.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_file_and_rename.diff
rename from rhodecode/tests/fixtures/hg_diff_mod_file_and_rename.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_file_and_rename.diff
diff --git a/rhodecode/tests/fixtures/git_diff_mod_single_binary_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_binary_file.diff
copy from rhodecode/tests/fixtures/git_diff_mod_single_binary_file.diff
copy to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_binary_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff
rename from rhodecode/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_no_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_no_newline.diff
rename from rhodecode/tests/fixtures/hg_diff_no_newline.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_no_newline.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_rename_and_chmod_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_and_chmod_file.diff
rename from rhodecode/tests/fixtures/hg_diff_rename_and_chmod_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_and_chmod_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_rename_file.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file.diff
rename from rhodecode/tests/fixtures/hg_diff_rename_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file_with_spaces.diff
rename from rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_rename_file_with_spaces.diff
diff --git a/rhodecode/tests/fixtures/hg_diff_single_file_change_newline.diff b/rhodecode/tests/fixtures/diff_fixtures/hg_diff_single_file_change_newline.diff
rename from rhodecode/tests/fixtures/hg_diff_single_file_change_newline.diff
rename to rhodecode/tests/fixtures/diff_fixtures/hg_diff_single_file_change_newline.diff
diff --git a/rhodecode/tests/fixtures/hg_node_history_response.json b/rhodecode/tests/fixtures/diff_fixtures/hg_node_history_response.json
rename from rhodecode/tests/fixtures/hg_node_history_response.json
rename to rhodecode/tests/fixtures/diff_fixtures/hg_node_history_response.json
diff --git a/rhodecode/tests/fixtures/journal_dump.csv b/rhodecode/tests/fixtures/diff_fixtures/journal_dump.csv
rename from rhodecode/tests/fixtures/journal_dump.csv
rename to rhodecode/tests/fixtures/diff_fixtures/journal_dump.csv
diff --git a/rhodecode/tests/fixtures/large_diff.diff b/rhodecode/tests/fixtures/diff_fixtures/large_diff.diff
rename from rhodecode/tests/fixtures/large_diff.diff
rename to rhodecode/tests/fixtures/diff_fixtures/large_diff.diff
diff --git a/rhodecode/tests/fixtures/svn_diff_binary_add_file.diff b/rhodecode/tests/fixtures/diff_fixtures/svn_diff_binary_add_file.diff
rename from rhodecode/tests/fixtures/svn_diff_binary_add_file.diff
rename to rhodecode/tests/fixtures/diff_fixtures/svn_diff_binary_add_file.diff
diff --git a/rhodecode/tests/fixtures/svn_diff_multiple_changes.diff b/rhodecode/tests/fixtures/diff_fixtures/svn_diff_multiple_changes.diff
rename from rhodecode/tests/fixtures/svn_diff_multiple_changes.diff
rename to rhodecode/tests/fixtures/diff_fixtures/svn_diff_multiple_changes.diff
diff --git a/rhodecode/tests/fixtures/svn_node_history_branches.json b/rhodecode/tests/fixtures/diff_fixtures/svn_node_history_branches.json
rename from rhodecode/tests/fixtures/svn_node_history_branches.json
rename to rhodecode/tests/fixtures/diff_fixtures/svn_node_history_branches.json
diff --git a/rhodecode/tests/fixtures/svn_node_history_response.json b/rhodecode/tests/fixtures/diff_fixtures/svn_node_history_response.json
rename from rhodecode/tests/fixtures/svn_node_history_response.json
rename to rhodecode/tests/fixtures/diff_fixtures/svn_node_history_response.json
diff --git a/rhodecode/tests/fixture_mods/fixture_pyramid.py b/rhodecode/tests/fixtures/fixture_pyramid.py
rename from rhodecode/tests/fixture_mods/fixture_pyramid.py
rename to rhodecode/tests/fixtures/fixture_pyramid.py
--- a/rhodecode/tests/fixture_mods/fixture_pyramid.py
+++ b/rhodecode/tests/fixtures/fixture_pyramid.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -20,61 +19,128 @@
import pytest
from rhodecode.lib.config_utils import get_app_config
-from rhodecode.tests.fixture import TestINI
+from rhodecode.tests.fixtures.rc_fixture import TestINI
from rhodecode.tests import TESTS_TMP_PATH
from rhodecode.tests.server_utils import RcVCSServer
+from rhodecode.tests.server_utils import RcWebServer
+from rhodecode.tests.server_utils import CeleryServer
-@pytest.fixture(scope='session')
-def vcsserver(request, vcsserver_port, vcsserver_factory):
- """
- Session scope VCSServer.
-
- Tests which need the VCSServer have to rely on this fixture in order
- to ensure it will be running.
-
- For specific needs, the fixture vcsserver_factory can be used. It allows to
- adjust the configuration file for the test run.
-
- Command line args:
-
- --without-vcsserver: Allows to switch this fixture off. You have to
- manually start the server.
-
- --vcsserver-port: Will expect the VCSServer to listen on this port.
- """
-
- if not request.config.getoption('with_vcsserver'):
- return None
-
- return vcsserver_factory(
- request, vcsserver_port=vcsserver_port)
-
-
-@pytest.fixture(scope='session')
-def vcsserver_factory(tmpdir_factory):
+@pytest.fixture(scope="session")
+def vcsserver_factory():
"""
Use this if you need a running vcsserver with a special configuration.
"""
- def factory(request, overrides=(), vcsserver_port=None,
- log_file=None, workers='3'):
-
- if vcsserver_port is None:
+ def factory(request, store_dir, overrides=(), config_file=None, port=None, log_file=None, workers="3", env=None, info_prefix=""):
+ env = env or {"RC_NO_TEST_ENV": "1"}
+ vcsserver_port = port
+ if port is None:
vcsserver_port = get_available_port()
overrides = list(overrides)
- overrides.append({'server:main': {'port': vcsserver_port}})
+ overrides.append({"server:main": {"port": vcsserver_port}})
+
+ if getattr(request, 'param', None):
+ config_overrides = [request.param]
+ overrides.extend(config_overrides)
+
+ option_name = "vcsserver_config"
+ override_option_name = None
+ if not config_file:
+ config_file = get_config(
+ request.config,
+ option_name=option_name,
+ override_option_name=override_option_name,
+ overrides=overrides,
+ basetemp=store_dir,
+ prefix=f"{info_prefix}test_vcsserver_ini_",
+ )
+ server = RcVCSServer(config_file, log_file, workers, env=env, info_prefix=info_prefix)
+ server.start()
+
+ @request.addfinalizer
+ def cleanup():
+ server.shutdown()
+
+ server.wait_until_ready()
+ return server
+
+ return factory
+
+
+@pytest.fixture(scope="session")
+def rhodecode_factory():
+ def factory(request, store_dir, overrides=(), config_file=None, port=None, log_file=None, workers="3", env=None, info_prefix=""):
+ env = env or {"RC_NO_TEST_ENV": "1"}
+ rhodecode_port = port
+ if port is None:
+ rhodecode_port = get_available_port()
+
+ overrides = list(overrides)
+ overrides.append({"server:main": {"port": rhodecode_port}})
+ overrides.append({"app:main": {"use_celery": "true"}})
+ overrides.append({"app:main": {"celery.task_always_eager": "false"}})
+
+ if getattr(request, 'param', None):
+ config_overrides = [request.param]
+ overrides.extend(config_overrides)
+
- option_name = 'vcsserver_config_http'
- override_option_name = 'vcsserver_config_override'
- config_file = get_config(
- request.config, option_name=option_name,
- override_option_name=override_option_name, overrides=overrides,
- basetemp=tmpdir_factory.getbasetemp().strpath,
- prefix='test_vcs_')
+ option_name = "rhodecode_config"
+ override_option_name = None
+ if not config_file:
+ config_file = get_config(
+ request.config,
+ option_name=option_name,
+ override_option_name=override_option_name,
+ overrides=overrides,
+ basetemp=store_dir,
+ prefix=f"{info_prefix}test_rhodecode_ini",
+ )
+
+ server = RcWebServer(config_file, log_file, workers, env, info_prefix=info_prefix)
+ server.start()
+
+ @request.addfinalizer
+ def cleanup():
+ server.shutdown()
+
+ server.wait_until_ready()
+ return server
+
+ return factory
+
- server = RcVCSServer(config_file, log_file, workers)
+@pytest.fixture(scope="session")
+def celery_factory():
+ def factory(request, store_dir, overrides=(), config_file=None, port=None, log_file=None, workers="3", env=None, info_prefix=""):
+ env = env or {"RC_NO_TEST_ENV": "1"}
+ rhodecode_port = port
+
+ overrides = list(overrides)
+ overrides.append({"app:main": {"use_celery": "true"}})
+ overrides.append({"app:main": {"celery.task_always_eager": "false"}})
+ config_overrides = None
+
+ if getattr(request, 'param', None):
+ config_overrides = [request.param]
+ overrides.extend(config_overrides)
+
+ option_name = "celery_config"
+ override_option_name = None
+
+ if not config_file:
+ config_file = get_config(
+ request.config,
+ option_name=option_name,
+ override_option_name=override_option_name,
+ overrides=overrides,
+ basetemp=store_dir,
+ prefix=f"{info_prefix}test_celery_ini_",
+ )
+
+ server = CeleryServer(config_file, log_file, workers, env, info_prefix=info_prefix)
server.start()
@request.addfinalizer
@@ -88,52 +154,68 @@ def vcsserver_factory(tmpdir_factory):
def _use_log_level(config):
- level = config.getoption('test_loglevel') or 'critical'
+ level = config.getoption("test_loglevel") or "critical"
return level.upper()
-@pytest.fixture(scope='session')
-def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
- option_name = 'pyramid_config'
+def _ini_config_factory(request, base_dir, rcserver_port, vcsserver_port):
+ option_name = "pyramid_config"
log_level = _use_log_level(request.config)
overrides = [
- {'server:main': {'port': rcserver_port}},
- {'app:main': {
- 'cache_dir': '%(here)s/rc-tests/rc_data',
- 'vcs.server': f'localhost:{vcsserver_port}',
- # johbo: We will always start the VCSServer on our own based on the
- # fixtures of the test cases. For the test run it must always be
- # off in the INI file.
- 'vcs.start_server': 'false',
-
- 'vcs.server.protocol': 'http',
- 'vcs.scm_app_implementation': 'http',
- 'vcs.svn.proxy.enabled': 'true',
- 'vcs.hooks.protocol.v2': 'celery',
- 'vcs.hooks.host': '*',
- 'repo_store.path': TESTS_TMP_PATH,
- 'app.service_api.token': 'service_secret_token',
- }},
-
- {'handler_console': {
- 'class': 'StreamHandler',
- 'args': '(sys.stderr,)',
- 'level': log_level,
- }},
-
+ {"server:main": {"port": rcserver_port}},
+ {
+ "app:main": {
+ #'cache_dir': '%(here)s/rc-tests/rc_data',
+ "vcs.server": f"localhost:{vcsserver_port}",
+ # johbo: We will always start the VCSServer on our own based on the
+ # fixtures of the test cases. For the test run it must always be
+ # off in the INI file.
+ "vcs.start_server": "false",
+ "vcs.server.protocol": "http",
+ "vcs.scm_app_implementation": "http",
+ "vcs.svn.proxy.enabled": "true",
+ "vcs.hooks.protocol.v2": "celery",
+ "vcs.hooks.host": "*",
+ "repo_store.path": TESTS_TMP_PATH,
+ "app.service_api.token": "service_secret_token",
+ }
+ },
+ {
+ "handler_console": {
+ "class": "StreamHandler",
+ "args": "(sys.stderr,)",
+ "level": log_level,
+ }
+ },
]
filename = get_config(
- request.config, option_name=option_name,
- override_option_name='{}_override'.format(option_name),
+ request.config,
+ option_name=option_name,
+ override_option_name=f"{option_name}_override",
overrides=overrides,
- basetemp=tmpdir_factory.getbasetemp().strpath,
- prefix='test_rce_')
+ basetemp=base_dir,
+ prefix="test_rce_",
+ )
return filename
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
+def ini_config(request, tmpdir_factory, rcserver_port, vcsserver_port):
+ base_dir = tmpdir_factory.getbasetemp().strpath
+ return _ini_config_factory(request, base_dir, rcserver_port, vcsserver_port)
+
+
+@pytest.fixture(scope="session")
+def ini_config_factory(request, tmpdir_factory, rcserver_port, vcsserver_port):
+ def _factory(ini_config_basedir, overrides=()):
+ return _ini_config_factory(request, ini_config_basedir, rcserver_port, vcsserver_port)
+
+ return _factory
+
+
+@pytest.fixture(scope="session")
def ini_settings(ini_config):
ini_path = ini_config
return get_app_config(ini_path)
@@ -141,26 +223,25 @@ def ini_settings(ini_config):
def get_available_port(min_port=40000, max_port=55555):
from rhodecode.lib.utils2 import get_available_port as _get_port
+
return _get_port(min_port, max_port)
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def rcserver_port(request):
port = get_available_port()
- print(f'Using rhodecode port {port}')
return port
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def vcsserver_port(request):
- port = request.config.getoption('--vcsserver-port')
+ port = request.config.getoption("--vcsserver-port")
if port is None:
port = get_available_port()
- print(f'Using vcsserver port {port}')
return port
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def available_port_factory() -> get_available_port:
"""
Returns a callable which returns free port numbers.
@@ -178,7 +259,7 @@ def available_port(available_port_factor
return available_port_factory()
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def testini_factory(tmpdir_factory, ini_config):
"""
Factory to create an INI file based on TestINI.
@@ -190,37 +271,38 @@ def testini_factory(tmpdir_factory, ini_
class TestIniFactory(object):
-
- def __init__(self, basetemp, template_ini):
- self._basetemp = basetemp
+ def __init__(self, ini_store_dir, template_ini):
+ self._ini_store_dir = ini_store_dir
self._template_ini = template_ini
- def __call__(self, ini_params, new_file_prefix='test'):
+ def __call__(self, ini_params, new_file_prefix="test"):
ini_file = TestINI(
- self._template_ini, ini_params=ini_params,
- new_file_prefix=new_file_prefix, dir=self._basetemp)
+ self._template_ini, ini_params=ini_params, new_file_prefix=new_file_prefix, dir=self._ini_store_dir
+ )
result = ini_file.create()
return result
-def get_config(
- config, option_name, override_option_name, overrides=None,
- basetemp=None, prefix='test'):
+def get_config(config, option_name, override_option_name, overrides=None, basetemp=None, prefix="test"):
"""
Find a configuration file and apply overrides for the given `prefix`.
"""
- config_file = (
- config.getoption(option_name) or config.getini(option_name))
+ try:
+ config_file = config.getoption(option_name)
+ except ValueError:
+ config_file = None
+
if not config_file:
- pytest.exit(
- "Configuration error, could not extract {}.".format(option_name))
+ config_file = config.getini(option_name)
+
+ if not config_file:
+ pytest.exit(f"Configuration error, could not extract {option_name}.")
overrides = overrides or []
- config_override = config.getoption(override_option_name)
- if config_override:
- overrides.append(config_override)
- temp_ini_file = TestINI(
- config_file, ini_params=overrides, new_file_prefix=prefix,
- dir=basetemp)
+ if override_option_name:
+ config_override = config.getoption(override_option_name)
+ if config_override:
+ overrides.append(config_override)
+ temp_ini_file = TestINI(config_file, ini_params=overrides, new_file_prefix=prefix, dir=basetemp)
return temp_ini_file.create()
diff --git a/rhodecode/tests/fixture_mods/fixture_utils.py b/rhodecode/tests/fixtures/fixture_utils.py
rename from rhodecode/tests/fixture_mods/fixture_utils.py
rename to rhodecode/tests/fixtures/fixture_utils.py
--- a/rhodecode/tests/fixture_mods/fixture_utils.py
+++ b/rhodecode/tests/fixtures/fixture_utils.py
@@ -1,5 +1,4 @@
-
-# Copyright (C) 2010-2023 RhodeCode GmbH
+# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
@@ -30,6 +29,7 @@ import uuid
import dateutil.tz
import logging
import functools
+import textwrap
import mock
import pyramid.testing
@@ -43,8 +43,17 @@ import rhodecode.lib
from rhodecode.model.changeset_status import ChangesetStatusModel
from rhodecode.model.comment import CommentsModel
from rhodecode.model.db import (
- PullRequest, PullRequestReviewers, Repository, RhodeCodeSetting, ChangesetStatus,
- RepoGroup, UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
+ PullRequest,
+ PullRequestReviewers,
+ Repository,
+ RhodeCodeSetting,
+ ChangesetStatus,
+ RepoGroup,
+ UserGroup,
+ RepoRhodeCodeUi,
+ RepoRhodeCodeSetting,
+ RhodeCodeUi,
+)
from rhodecode.model.meta import Session
from rhodecode.model.pull_request import PullRequestModel
from rhodecode.model.repo import RepoModel
@@ -60,12 +69,20 @@ from rhodecode.lib.str_utils import safe
from rhodecode.lib.hash_utils import sha1_safe
from rhodecode.lib.vcs.backends import get_backend
from rhodecode.lib.vcs.nodes import FileNode
+from rhodecode.lib.base import bootstrap_config
from rhodecode.tests import (
- login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
- TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
- TEST_USER_REGULAR_PASS)
-from rhodecode.tests.utils import CustomTestApp, set_anonymous_access
-from rhodecode.tests.fixture import Fixture
+ login_user_session,
+ get_new_dir,
+ utils,
+ TESTS_TMP_PATH,
+ TEST_USER_ADMIN_LOGIN,
+ TEST_USER_REGULAR_LOGIN,
+ TEST_USER_REGULAR2_LOGIN,
+ TEST_USER_REGULAR_PASS,
+ console_printer,
+)
+from rhodecode.tests.utils import set_anonymous_access
+from rhodecode.tests.fixtures.rc_fixture import Fixture
from rhodecode.config import utils as config_utils
log = logging.getLogger(__name__)
@@ -76,36 +93,7 @@ def cmp(a, b):
return (a > b) - (a < b)
-@pytest.fixture(scope='session', autouse=True)
-def activate_example_rcextensions(request):
- """
- Patch in an example rcextensions module which verifies passed in kwargs.
- """
- from rhodecode.config import rcextensions
-
- old_extensions = rhodecode.EXTENSIONS
- rhodecode.EXTENSIONS = rcextensions
- rhodecode.EXTENSIONS.calls = collections.defaultdict(list)
-
- @request.addfinalizer
- def cleanup():
- rhodecode.EXTENSIONS = old_extensions
-
-
-@pytest.fixture()
-def capture_rcextensions():
- """
- Returns the recorded calls to entry points in rcextensions.
- """
- calls = rhodecode.EXTENSIONS.calls
- calls.clear()
- # Note: At this moment, it is still the empty dict, but that will
- # be filled during the test run and since it is a reference this
- # is enough to make it work.
- return calls
-
-
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def http_environ_session():
"""
Allow to use "http_environ" in session scope.
@@ -117,7 +105,31 @@ def plain_http_host_stub():
"""
Value of HTTP_HOST in the test run.
"""
- return 'example.com:80'
+ return "example.com:80"
+
+
+def plain_config_stub(request, request_stub):
+ """
+ Set up pyramid.testing and return the Configurator.
+ """
+
+ config = bootstrap_config(request=request_stub)
+
+ @request.addfinalizer
+ def cleanup():
+ pyramid.testing.tearDown()
+
+ return config
+
+
+def plain_request_stub():
+ """
+ Stub request object.
+ """
+ from rhodecode.lib.base import bootstrap_request
+
+ _request = bootstrap_request(scheme="https")
+ return _request
@pytest.fixture()
@@ -132,7 +144,7 @@ def plain_http_host_only_stub():
"""
Value of HTTP_HOST in the test run.
"""
- return plain_http_host_stub().split(':')[0]
+ return plain_http_host_stub().split(":")[0]
@pytest.fixture()
@@ -147,56 +159,58 @@ def plain_http_environ():
"""
HTTP extra environ keys.
- User by the test application and as well for setting up the pylons
+ Used by the test application and as well for setting up the pylons
environment. In the case of the fixture "app" it should be possible
to override this for a specific test case.
"""
return {
- 'SERVER_NAME': plain_http_host_only_stub(),
- 'SERVER_PORT': plain_http_host_stub().split(':')[1],
- 'HTTP_HOST': plain_http_host_stub(),
- 'HTTP_USER_AGENT': 'rc-test-agent',
- 'REQUEST_METHOD': 'GET'
+ "SERVER_NAME": plain_http_host_only_stub(),
+ "SERVER_PORT": plain_http_host_stub().split(":")[1],
+ "HTTP_HOST": plain_http_host_stub(),
+ "HTTP_USER_AGENT": "rc-test-agent",
+ "REQUEST_METHOD": "GET",
}
-@pytest.fixture()
-def http_environ():
- """
- HTTP extra environ keys.
-
- User by the test application and as well for setting up the pylons
- environment. In the case of the fixture "app" it should be possible
- to override this for a specific test case.
- """
- return plain_http_environ()
-
-
-@pytest.fixture(scope='session')
-def baseapp(ini_config, vcsserver, http_environ_session):
+@pytest.fixture(scope="session")
+def baseapp(request, ini_config, http_environ_session, available_port_factory, vcsserver_factory, celery_factory):
from rhodecode.lib.config_utils import get_app_config
from rhodecode.config.middleware import make_pyramid_app
- log.info("Using the RhodeCode configuration:{}".format(ini_config))
+ log.info("Using the RhodeCode configuration:%s", ini_config)
pyramid.paster.setup_logging(ini_config)
settings = get_app_config(ini_config)
- app = make_pyramid_app({'__file__': ini_config}, **settings)
+ store_dir = os.path.dirname(ini_config)
+
+ # start vcsserver
+ _vcsserver_port = available_port_factory()
+ vcsserver_instance = vcsserver_factory(request, store_dir=store_dir, port=_vcsserver_port, info_prefix="base-app-")
+
+ settings["vcs.server"] = vcsserver_instance.bind_addr
+
+ # we skip setting store_dir for baseapp, it's internally set via testing rhodecode.ini
+ # settings['repo_store.path'] = str(store_dir)
+ console_printer(f" :warning: [green]pytest-setup[/green] Starting base pyramid-app: {ini_config}")
+ pyramid_baseapp = make_pyramid_app({"__file__": ini_config}, **settings)
- return app
+ # start celery
+ celery_factory(
+ request,
+ store_dir=store_dir,
+ port=None,
+ info_prefix="base-app-",
+ overrides=(
+ {"handler_console": {"level": "DEBUG"}},
+ {"app:main": {"vcs.server": vcsserver_instance.bind_addr}},
+ {"app:main": {"repo_store.path": store_dir}},
+ ),
+ )
+
+ return pyramid_baseapp
-@pytest.fixture(scope='function')
-def app(request, config_stub, baseapp, http_environ):
- app = CustomTestApp(
- baseapp,
- extra_environ=http_environ)
- if request.cls:
- request.cls.app = app
- return app
-
-
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def app_settings(baseapp, ini_config):
"""
Settings dictionary used to create the app.
@@ -207,19 +221,19 @@ def app_settings(baseapp, ini_config):
return baseapp.config.get_settings()
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def db_connection(ini_settings):
# Initialize the database connection.
config_utils.initialize_database(ini_settings)
-LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
+LoginData = collections.namedtuple("LoginData", ("csrf_token", "user"))
def _autologin_user(app, *args):
session = login_user_session(app, *args)
csrf_token = rhodecode.lib.auth.get_csrf_token(session)
- return LoginData(csrf_token, session['rhodecode_user'])
+ return LoginData(csrf_token, session["rhodecode_user"])
@pytest.fixture()
@@ -235,18 +249,17 @@ def autologin_regular_user(app):
"""
Utility fixture which makes sure that the regular user is logged in
"""
- return _autologin_user(
- app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+ return _autologin_user(app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-@pytest.fixture(scope='function')
+@pytest.fixture(scope="function")
def csrf_token(request, autologin_user):
return autologin_user.csrf_token
-@pytest.fixture(scope='function')
+@pytest.fixture(scope="function")
def xhr_header(request):
- return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+ return {"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}
@pytest.fixture()
@@ -257,18 +270,18 @@ def real_crypto_backend(monkeypatch):
During the test run the crypto backend is replaced with a faster
implementation based on the MD5 algorithm.
"""
- monkeypatch.setattr(rhodecode, 'is_test', False)
+ monkeypatch.setattr(rhodecode, "is_test", False)
-@pytest.fixture(scope='class')
+@pytest.fixture(scope="class")
def index_location(request, baseapp):
- index_location = baseapp.config.get_settings()['search.location']
+ index_location = baseapp.config.get_settings()["search.location"]
if request.cls:
request.cls.index_location = index_location
return index_location
-@pytest.fixture(scope='session', autouse=True)
+@pytest.fixture(scope="session", autouse=True)
def tests_tmp_path(request):
"""
Create temporary directory to be used during the test session.
@@ -276,7 +289,8 @@ def tests_tmp_path(request):
if not os.path.exists(TESTS_TMP_PATH):
os.makedirs(TESTS_TMP_PATH)
- if not request.config.getoption('--keep-tmp-path'):
+ if not request.config.getoption("--keep-tmp-path"):
+
@request.addfinalizer
def remove_tmp_path():
shutil.rmtree(TESTS_TMP_PATH)
@@ -291,7 +305,7 @@ def test_repo_group(request):
usage automatically
"""
fixture = Fixture()
- repogroupid = 'test_repo_group_%s' % str(time.time()).replace('.', '')
+ repogroupid = "test_repo_group_%s" % str(time.time()).replace(".", "")
repo_group = fixture.create_repo_group(repogroupid)
def _cleanup():
@@ -308,7 +322,7 @@ def test_user_group(request):
usage automatically
"""
fixture = Fixture()
- usergroupid = 'test_user_group_%s' % str(time.time()).replace('.', '')
+ usergroupid = "test_user_group_%s" % str(time.time()).replace(".", "")
user_group = fixture.create_user_group(usergroupid)
def _cleanup():
@@ -318,7 +332,7 @@ def test_user_group(request):
return user_group
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def test_repo(request):
container = TestRepoContainer()
request.addfinalizer(container._cleanup)
@@ -340,9 +354,9 @@ class TestRepoContainer(object):
"""
dump_extractors = {
- 'git': utils.extract_git_repo_from_dump,
- 'hg': utils.extract_hg_repo_from_dump,
- 'svn': utils.extract_svn_repo_from_dump,
+ "git": utils.extract_git_repo_from_dump,
+ "hg": utils.extract_hg_repo_from_dump,
+ "svn": utils.extract_svn_repo_from_dump,
}
def __init__(self):
@@ -358,7 +372,7 @@ class TestRepoContainer(object):
return Repository.get(self._repos[key])
def _create_repo(self, dump_name, backend_alias, config):
- repo_name = f'{backend_alias}-{dump_name}'
+ repo_name = f"{backend_alias}-{dump_name}"
backend = get_backend(backend_alias)
dump_extractor = self.dump_extractors[backend_alias]
repo_path = dump_extractor(dump_name, repo_name)
@@ -375,19 +389,17 @@ class TestRepoContainer(object):
self._fixture.destroy_repo(repo_name)
-def backend_base(request, backend_alias, baseapp, test_repo):
- if backend_alias not in request.config.getoption('--backends'):
- pytest.skip("Backend %s not selected." % (backend_alias, ))
+def backend_base(request, backend_alias, test_repo):
+ if backend_alias not in request.config.getoption("--backends"):
+ pytest.skip(f"Backend {backend_alias} not selected.")
utils.check_xfail_backends(request.node, backend_alias)
utils.check_skip_backends(request.node, backend_alias)
- repo_name = 'vcs_test_%s' % (backend_alias, )
+ repo_name = f"vcs_test_{backend_alias}"
backend = Backend(
- alias=backend_alias,
- repo_name=repo_name,
- test_name=request.node.name,
- test_repo_container=test_repo)
+ alias=backend_alias, repo_name=repo_name, test_name=request.node.name, test_repo_container=test_repo
+ )
request.addfinalizer(backend.cleanup)
return backend
@@ -404,22 +416,22 @@ def backend(request, backend_alias, base
for specific backends. This is intended as a utility for incremental
development of a new backend implementation.
"""
- return backend_base(request, backend_alias, baseapp, test_repo)
+ return backend_base(request, backend_alias, test_repo)
@pytest.fixture()
def backend_git(request, baseapp, test_repo):
- return backend_base(request, 'git', baseapp, test_repo)
+ return backend_base(request, "git", test_repo)
@pytest.fixture()
def backend_hg(request, baseapp, test_repo):
- return backend_base(request, 'hg', baseapp, test_repo)
+ return backend_base(request, "hg", test_repo)
@pytest.fixture()
def backend_svn(request, baseapp, test_repo):
- return backend_base(request, 'svn', baseapp, test_repo)
+ return backend_base(request, "svn", test_repo)
@pytest.fixture()
@@ -467,9 +479,9 @@ class Backend(object):
session.
"""
- invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
+ invalid_repo_name = re.compile(r"[^0-9a-zA-Z]+")
_master_repo = None
- _master_repo_path = ''
+ _master_repo_path = ""
_commit_ids = {}
def __init__(self, alias, repo_name, test_name, test_repo_container):
@@ -500,6 +512,7 @@ class Backend(object):
last repo which has been created with `create_repo`.
"""
from rhodecode.model.db import Repository
+
return Repository.get_by_repo_name(self.repo_name)
@property
@@ -517,9 +530,7 @@ class Backend(object):
which can serve as the base to create a new commit on top of it.
"""
vcsrepo = self.repo.scm_instance()
- head_id = (
- vcsrepo.DEFAULT_BRANCH_NAME or
- vcsrepo.commit_ids[-1])
+ head_id = vcsrepo.DEFAULT_BRANCH_NAME or vcsrepo.commit_ids[-1]
return head_id
@property
@@ -543,9 +554,7 @@ class Backend(object):
return self._commit_ids
- def create_repo(
- self, commits=None, number_of_commits=0, heads=None,
- name_suffix='', bare=False, **kwargs):
+ def create_repo(self, commits=None, number_of_commits=0, heads=None, name_suffix="", bare=False, **kwargs):
"""
Create a repository and record it for later cleanup.
@@ -559,13 +568,10 @@ class Backend(object):
:param bare: set a repo as bare (no checkout)
"""
self.repo_name = self._next_repo_name() + name_suffix
- repo = self._fixture.create_repo(
- self.repo_name, repo_type=self.alias, bare=bare, **kwargs)
+ repo = self._fixture.create_repo(self.repo_name, repo_type=self.alias, bare=bare, **kwargs)
self._cleanup_repos.append(repo.repo_name)
- commits = commits or [
- {'message': f'Commit {x} of {self.repo_name}'}
- for x in range(number_of_commits)]
+ commits = commits or [{"message": f"Commit {x} of {self.repo_name}"} for x in range(number_of_commits)]
vcs_repo = repo.scm_instance()
vcs_repo.count()
self._add_commits_to_repo(vcs_repo, commits)
@@ -579,7 +585,7 @@ class Backend(object):
Make sure that repo contains all commits mentioned in `heads`
"""
vcsrepo = repo.scm_instance()
- vcsrepo.config.clear_section('hooks')
+ vcsrepo.config.clear_section("hooks")
commit_ids = [self._commit_ids[h] for h in heads]
if do_fetch:
vcsrepo.fetch(self._master_repo_path, commit_ids=commit_ids)
@@ -592,21 +598,22 @@ class Backend(object):
self._cleanup_repos.append(self.repo_name)
return repo
- def new_repo_name(self, suffix=''):
+ def new_repo_name(self, suffix=""):
self.repo_name = self._next_repo_name() + suffix
self._cleanup_repos.append(self.repo_name)
return self.repo_name
def _next_repo_name(self):
- return "%s_%s" % (
- self.invalid_repo_name.sub('_', self._test_name), len(self._cleanup_repos))
+ return "%s_%s" % (self.invalid_repo_name.sub("_", self._test_name), len(self._cleanup_repos))
- def ensure_file(self, filename, content=b'Test content\n'):
+ def ensure_file(self, filename, content=b"Test content\n"):
assert self._cleanup_repos, "Avoid writing into vcs_test repos"
commits = [
- {'added': [
- FileNode(filename, content=content),
- ]},
+ {
+ "added": [
+ FileNode(filename, content=content),
+ ]
+ },
]
self._add_commits_to_repo(self.repo.scm_instance(), commits)
@@ -627,11 +634,11 @@ class Backend(object):
self._commit_ids = commit_ids
# Creating refs for Git to allow fetching them from remote repository
- if self.alias == 'git':
+ if self.alias == "git":
refs = {}
for message in self._commit_ids:
- cleanup_message = message.replace(' ', '')
- ref_name = f'refs/test-refs/{cleanup_message}'
+ cleanup_message = message.replace(" ", "")
+ ref_name = f"refs/test-refs/{cleanup_message}"
refs[ref_name] = self._commit_ids[message]
self._create_refs(repo, refs)
@@ -645,7 +652,7 @@ class VcsBackend(object):
Represents the test configuration for one supported vcs backend.
"""
- invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
+ invalid_repo_name = re.compile(r"[^0-9a-zA-Z]+")
def __init__(self, alias, repo_path, test_name, test_repo_container):
self.alias = alias
@@ -658,7 +665,7 @@ class VcsBackend(object):
return self._test_repo_container(key, self.alias).scm_instance()
def __repr__(self):
- return f'{self.__class__.__name__}(alias={self.alias}, repo={self._repo_path})'
+ return f"{self.__class__.__name__}(alias={self.alias}, repo={self._repo_path})"
@property
def repo(self):
@@ -676,8 +683,7 @@ class VcsBackend(object):
"""
return get_backend(self.alias)
- def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None,
- bare=False):
+ def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None, bare=False):
repo_name = self._next_repo_name()
self._repo_path = get_new_dir(repo_name)
repo_class = get_backend(self.alias)
@@ -687,9 +693,7 @@ class VcsBackend(object):
repo = repo_class(self._repo_path, create=True, src_url=src_url, bare=bare)
self._cleanup_repos.append(repo)
- commits = commits or [
- {'message': 'Commit %s of %s' % (x, repo_name)}
- for x in range(number_of_commits)]
+ commits = commits or [{"message": f"Commit {x} of {repo_name}"} for x in range(number_of_commits)]
_add_commits_to_repo(repo, commits)
return repo
@@ -706,38 +710,30 @@ class VcsBackend(object):
return self._repo_path
def _next_repo_name(self):
+ return "{}_{}".format(self.invalid_repo_name.sub("_", self._test_name), len(self._cleanup_repos))
- return "{}_{}".format(
- self.invalid_repo_name.sub('_', self._test_name),
- len(self._cleanup_repos)
- )
-
- def add_file(self, repo, filename, content='Test content\n'):
+ def add_file(self, repo, filename, content="Test content\n"):
imc = repo.in_memory_commit
imc.add(FileNode(safe_bytes(filename), content=safe_bytes(content)))
- imc.commit(
- message='Automatic commit from vcsbackend fixture',
- author='Automatic
')
+ imc.commit(message="Automatic commit from vcsbackend fixture", author="Automatic ")
- def ensure_file(self, filename, content='Test content\n'):
+ def ensure_file(self, filename, content="Test content\n"):
assert self._cleanup_repos, "Avoid writing into vcs_test repos"
self.add_file(self.repo, filename, content)
def vcsbackend_base(request, backend_alias, tests_tmp_path, baseapp, test_repo) -> VcsBackend:
- if backend_alias not in request.config.getoption('--backends'):
- pytest.skip("Backend %s not selected." % (backend_alias, ))
+ if backend_alias not in request.config.getoption("--backends"):
+ pytest.skip(f"Backend {backend_alias} not selected.")
utils.check_xfail_backends(request.node, backend_alias)
utils.check_skip_backends(request.node, backend_alias)
- repo_name = f'vcs_test_{backend_alias}'
+ repo_name = f"vcs_test_{backend_alias}"
repo_path = os.path.join(tests_tmp_path, repo_name)
backend = VcsBackend(
- alias=backend_alias,
- repo_path=repo_path,
- test_name=request.node.name,
- test_repo_container=test_repo)
+ alias=backend_alias, repo_path=repo_path, test_name=request.node.name, test_repo_container=test_repo
+ )
request.addfinalizer(backend.cleanup)
return backend
@@ -758,17 +754,17 @@ def vcsbackend(request, backend_alias, t
@pytest.fixture()
def vcsbackend_git(request, tests_tmp_path, baseapp, test_repo):
- return vcsbackend_base(request, 'git', tests_tmp_path, baseapp, test_repo)
+ return vcsbackend_base(request, "git", tests_tmp_path, baseapp, test_repo)
@pytest.fixture()
def vcsbackend_hg(request, tests_tmp_path, baseapp, test_repo):
- return vcsbackend_base(request, 'hg', tests_tmp_path, baseapp, test_repo)
+ return vcsbackend_base(request, "hg", tests_tmp_path, baseapp, test_repo)
@pytest.fixture()
def vcsbackend_svn(request, tests_tmp_path, baseapp, test_repo):
- return vcsbackend_base(request, 'svn', tests_tmp_path, baseapp, test_repo)
+ return vcsbackend_base(request, "svn", tests_tmp_path, baseapp, test_repo)
@pytest.fixture()
@@ -789,29 +785,28 @@ def _add_commits_to_repo(vcs_repo, commi
imc = vcs_repo.in_memory_commit
for idx, commit in enumerate(commits):
- message = str(commit.get('message', f'Commit {idx}'))
+ message = str(commit.get("message", f"Commit {idx}"))
- for node in commit.get('added', []):
+ for node in commit.get("added", []):
imc.add(FileNode(safe_bytes(node.path), content=node.content))
- for node in commit.get('changed', []):
+ for node in commit.get("changed", []):
imc.change(FileNode(safe_bytes(node.path), content=node.content))
- for node in commit.get('removed', []):
+ for node in commit.get("removed", []):
imc.remove(FileNode(safe_bytes(node.path)))
- parents = [
- vcs_repo.get_commit(commit_id=commit_ids[p])
- for p in commit.get('parents', [])]
+ parents = [vcs_repo.get_commit(commit_id=commit_ids[p]) for p in commit.get("parents", [])]
- operations = ('added', 'changed', 'removed')
+ operations = ("added", "changed", "removed")
if not any((commit.get(o) for o in operations)):
- imc.add(FileNode(b'file_%b' % safe_bytes(str(idx)), content=safe_bytes(message)))
+ imc.add(FileNode(b"file_%b" % safe_bytes(str(idx)), content=safe_bytes(message)))
commit = imc.commit(
message=message,
- author=str(commit.get('author', 'Automatic ')),
- date=commit.get('date'),
- branch=commit.get('branch'),
- parents=parents)
+ author=str(commit.get("author", "Automatic ")),
+ date=commit.get("date"),
+ branch=commit.get("branch"),
+ parents=parents,
+ )
commit_ids[commit.message] = commit.raw_id
@@ -842,14 +837,14 @@ class RepoServer(object):
self._cleanup_servers = []
def serve(self, vcsrepo):
- if vcsrepo.alias != 'svn':
- raise TypeError("Backend %s not supported" % vcsrepo.alias)
+ if vcsrepo.alias != "svn":
+ raise TypeError(f"Backend {vcsrepo.alias} not supported")
proc = subprocess.Popen(
- ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
- '--root', vcsrepo.path])
+ ["svnserve", "-d", "--foreground", "--listen-host", "localhost", "--root", vcsrepo.path]
+ )
self._cleanup_servers.append(proc)
- self.url = 'svn://localhost'
+ self.url = "svn://localhost"
def cleanup(self):
for proc in self._cleanup_servers:
@@ -874,7 +869,6 @@ def pr_util(backend, request, config_stu
class PRTestUtility(object):
-
pull_request = None
pull_request_id = None
mergeable_patcher = None
@@ -886,48 +880,55 @@ class PRTestUtility(object):
self.backend = backend
def create_pull_request(
- self, commits=None, target_head=None, source_head=None,
- revisions=None, approved=False, author=None, mergeable=False,
- enable_notifications=True, name_suffix='', reviewers=None, observers=None,
- title="Test", description="Description"):
+ self,
+ commits=None,
+ target_head=None,
+ source_head=None,
+ revisions=None,
+ approved=False,
+ author=None,
+ mergeable=False,
+ enable_notifications=True,
+ name_suffix="",
+ reviewers=None,
+ observers=None,
+ title="Test",
+ description="Description",
+ ):
self.set_mergeable(mergeable)
if not enable_notifications:
# mock notification side effect
- self.notification_patcher = mock.patch(
- 'rhodecode.model.notification.NotificationModel.create')
+ self.notification_patcher = mock.patch("rhodecode.model.notification.NotificationModel.create")
self.notification_patcher.start()
if not self.pull_request:
if not commits:
commits = [
- {'message': 'c1'},
- {'message': 'c2'},
- {'message': 'c3'},
+ {"message": "c1"},
+ {"message": "c2"},
+ {"message": "c3"},
]
- target_head = 'c1'
- source_head = 'c2'
- revisions = ['c2']
+ target_head = "c1"
+ source_head = "c2"
+ revisions = ["c2"]
self.commit_ids = self.backend.create_master_repo(commits)
- self.target_repository = self.backend.create_repo(
- heads=[target_head], name_suffix=name_suffix)
- self.source_repository = self.backend.create_repo(
- heads=[source_head], name_suffix=name_suffix)
- self.author = author or UserModel().get_by_username(
- TEST_USER_ADMIN_LOGIN)
+ self.target_repository = self.backend.create_repo(heads=[target_head], name_suffix=name_suffix)
+ self.source_repository = self.backend.create_repo(heads=[source_head], name_suffix=name_suffix)
+ self.author = author or UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
model = PullRequestModel()
self.create_parameters = {
- 'created_by': self.author,
- 'source_repo': self.source_repository.repo_name,
- 'source_ref': self._default_branch_reference(source_head),
- 'target_repo': self.target_repository.repo_name,
- 'target_ref': self._default_branch_reference(target_head),
- 'revisions': [self.commit_ids[r] for r in revisions],
- 'reviewers': reviewers or self._get_reviewers(),
- 'observers': observers or self._get_observers(),
- 'title': title,
- 'description': description,
+ "created_by": self.author,
+ "source_repo": self.source_repository.repo_name,
+ "source_ref": self._default_branch_reference(source_head),
+ "target_repo": self.target_repository.repo_name,
+ "target_ref": self._default_branch_reference(target_head),
+ "revisions": [self.commit_ids[r] for r in revisions],
+ "reviewers": reviewers or self._get_reviewers(),
+ "observers": observers or self._get_observers(),
+ "title": title,
+ "description": description,
}
self.pull_request = model.create(**self.create_parameters)
assert model.get_versions(self.pull_request) == []
@@ -943,9 +944,7 @@ class PRTestUtility(object):
return self.pull_request
def approve(self):
- self.create_status_votes(
- ChangesetStatus.STATUS_APPROVED,
- *self.pull_request.reviewers)
+ self.create_status_votes(ChangesetStatus.STATUS_APPROVED, *self.pull_request.reviewers)
def close(self):
PullRequestModel().close_pull_request(self.pull_request, self.author)
@@ -953,28 +952,26 @@ class PRTestUtility(object):
def _default_branch_reference(self, commit_message, branch: str = None) -> str:
default_branch = branch or self.backend.default_branch_name
message = self.commit_ids[commit_message]
- reference = f'branch:{default_branch}:{message}'
+ reference = f"branch:{default_branch}:{message}"
return reference
def _get_reviewers(self):
role = PullRequestReviewers.ROLE_REVIEWER
return [
- (TEST_USER_REGULAR_LOGIN, ['default1'], False, role, []),
- (TEST_USER_REGULAR2_LOGIN, ['default2'], False, role, []),
+ (TEST_USER_REGULAR_LOGIN, ["default1"], False, role, []),
+ (TEST_USER_REGULAR2_LOGIN, ["default2"], False, role, []),
]
def _get_observers(self):
- return [
-
- ]
+ return []
def update_source_repository(self, head=None, do_fetch=False):
- heads = [head or 'c3']
+ heads = [head or "c3"]
self.backend.pull_heads(self.source_repository, heads=heads, do_fetch=do_fetch)
def update_target_repository(self, head=None, do_fetch=False):
- heads = [head or 'c3']
+ heads = [head or "c3"]
self.backend.pull_heads(self.target_repository, heads=heads, do_fetch=do_fetch)
def set_pr_target_ref(self, ref_type: str = "branch", ref_name: str = "branch", ref_commit_id: str = "") -> str:
@@ -1004,7 +1001,7 @@ class PRTestUtility(object):
# TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
# remove the if once that's sorted out.
if self.backend.alias == "git":
- kwargs = {'branch_name': self.backend.default_branch_name}
+ kwargs = {"branch_name": self.backend.default_branch_name}
else:
kwargs = {}
source_vcs.strip(removed_commit_id, **kwargs)
@@ -1015,10 +1012,8 @@ class PRTestUtility(object):
def create_comment(self, linked_to=None):
comment = CommentsModel().create(
- text="Test comment",
- repo=self.target_repository.repo_name,
- user=self.author,
- pull_request=self.pull_request)
+ text="Test comment", repo=self.target_repository.repo_name, user=self.author, pull_request=self.pull_request
+ )
assert comment.pull_request_version_id is None
if linked_to:
@@ -1026,15 +1021,15 @@ class PRTestUtility(object):
return comment
- def create_inline_comment(
- self, linked_to=None, line_no='n1', file_path='file_1'):
+ def create_inline_comment(self, linked_to=None, line_no="n1", file_path="file_1"):
comment = CommentsModel().create(
text="Test comment",
repo=self.target_repository.repo_name,
user=self.author,
line_no=line_no,
f_path=file_path,
- pull_request=self.pull_request)
+ pull_request=self.pull_request,
+ )
assert comment.pull_request_version_id is None
if linked_to:
@@ -1044,25 +1039,20 @@ class PRTestUtility(object):
def create_version_of_pull_request(self):
pull_request = self.create_pull_request()
- version = PullRequestModel()._create_version_from_snapshot(
- pull_request)
+ version = PullRequestModel()._create_version_from_snapshot(pull_request)
return version
def create_status_votes(self, status, *reviewers):
for reviewer in reviewers:
ChangesetStatusModel().set_status(
- repo=self.pull_request.target_repo,
- status=status,
- user=reviewer.user_id,
- pull_request=self.pull_request)
+ repo=self.pull_request.target_repo, status=status, user=reviewer.user_id, pull_request=self.pull_request
+ )
def set_mergeable(self, value):
if not self.mergeable_patcher:
- self.mergeable_patcher = mock.patch.object(
- VcsSettingsModel, 'get_general_settings')
+ self.mergeable_patcher = mock.patch.object(VcsSettingsModel, "get_general_settings")
self.mergeable_mock = self.mergeable_patcher.start()
- self.mergeable_mock.return_value = {
- 'rhodecode_pr_merge_enabled': value}
+ self.mergeable_mock.return_value = {"rhodecode_pr_merge_enabled": value}
def cleanup(self):
# In case the source repository is already cleaned up, the pull
@@ -1109,7 +1099,6 @@ def user_util(request, db_connection):
# TODO: johbo: Split this up into utilities per domain or something similar
class UserUtility(object):
-
def __init__(self, test_name="test"):
self._test_name = self._sanitize_name(test_name)
self.fixture = Fixture()
@@ -1126,37 +1115,29 @@ class UserUtility(object):
self.user_permissions = []
def _sanitize_name(self, name):
- for char in ['[', ']']:
- name = name.replace(char, '_')
+ for char in ["[", "]"]:
+ name = name.replace(char, "_")
return name
- def create_repo_group(
- self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
- group_name = "{prefix}_repogroup_{count}".format(
- prefix=self._test_name,
- count=len(self.repo_group_ids))
- repo_group = self.fixture.create_repo_group(
- group_name, cur_user=owner)
+ def create_repo_group(self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
+ group_name = f"{self._test_name}_repogroup_{len(self.repo_group_ids)}"
+ repo_group = self.fixture.create_repo_group(group_name, cur_user=owner)
if auto_cleanup:
self.repo_group_ids.append(repo_group.group_id)
return repo_group
- def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None,
- auto_cleanup=True, repo_type='hg', bare=False):
- repo_name = "{prefix}_repository_{count}".format(
- prefix=self._test_name,
- count=len(self.repos_ids))
+ def create_repo(self, owner=TEST_USER_ADMIN_LOGIN, parent=None, auto_cleanup=True, repo_type="hg", bare=False):
+ repo_name = f"{self._test_name}_repository_{len(self.repos_ids)}"
repository = self.fixture.create_repo(
- repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type, bare=bare)
+ repo_name, cur_user=owner, repo_group=parent, repo_type=repo_type, bare=bare
+ )
if auto_cleanup:
self.repos_ids.append(repository.repo_id)
return repository
def create_user(self, auto_cleanup=True, **kwargs):
- user_name = "{prefix}_user_{count}".format(
- prefix=self._test_name,
- count=len(self.user_ids))
+ user_name = f"{self._test_name}_user_{len(self.user_ids)}"
user = self.fixture.create_user(user_name, **kwargs)
if auto_cleanup:
self.user_ids.append(user.user_id)
@@ -1171,13 +1152,9 @@ class UserUtility(object):
user_group = self.create_user_group(members=[user])
return user, user_group
- def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None,
- auto_cleanup=True, **kwargs):
- group_name = "{prefix}_usergroup_{count}".format(
- prefix=self._test_name,
- count=len(self.user_group_ids))
- user_group = self.fixture.create_user_group(
- group_name, cur_user=owner, **kwargs)
+ def create_user_group(self, owner=TEST_USER_ADMIN_LOGIN, members=None, auto_cleanup=True, **kwargs):
+ group_name = f"{self._test_name}_usergroup_{len(self.user_group_ids)}"
+ user_group = self.fixture.create_user_group(group_name, cur_user=owner, **kwargs)
if auto_cleanup:
self.user_group_ids.append(user_group.users_group_id)
@@ -1190,52 +1167,34 @@ class UserUtility(object):
self.inherit_default_user_permissions(user_name, False)
self.user_permissions.append((user_name, permission_name))
- def grant_user_permission_to_repo_group(
- self, repo_group, user, permission_name):
- permission = RepoGroupModel().grant_user_permission(
- repo_group, user, permission_name)
- self.user_repo_group_permission_ids.append(
- (repo_group.group_id, user.user_id))
+ def grant_user_permission_to_repo_group(self, repo_group, user, permission_name):
+ permission = RepoGroupModel().grant_user_permission(repo_group, user, permission_name)
+ self.user_repo_group_permission_ids.append((repo_group.group_id, user.user_id))
return permission
- def grant_user_group_permission_to_repo_group(
- self, repo_group, user_group, permission_name):
- permission = RepoGroupModel().grant_user_group_permission(
- repo_group, user_group, permission_name)
- self.user_group_repo_group_permission_ids.append(
- (repo_group.group_id, user_group.users_group_id))
+ def grant_user_group_permission_to_repo_group(self, repo_group, user_group, permission_name):
+ permission = RepoGroupModel().grant_user_group_permission(repo_group, user_group, permission_name)
+ self.user_group_repo_group_permission_ids.append((repo_group.group_id, user_group.users_group_id))
return permission
- def grant_user_permission_to_repo(
- self, repo, user, permission_name):
- permission = RepoModel().grant_user_permission(
- repo, user, permission_name)
- self.user_repo_permission_ids.append(
- (repo.repo_id, user.user_id))
+ def grant_user_permission_to_repo(self, repo, user, permission_name):
+ permission = RepoModel().grant_user_permission(repo, user, permission_name)
+ self.user_repo_permission_ids.append((repo.repo_id, user.user_id))
return permission
- def grant_user_group_permission_to_repo(
- self, repo, user_group, permission_name):
- permission = RepoModel().grant_user_group_permission(
- repo, user_group, permission_name)
- self.user_group_repo_permission_ids.append(
- (repo.repo_id, user_group.users_group_id))
+ def grant_user_group_permission_to_repo(self, repo, user_group, permission_name):
+ permission = RepoModel().grant_user_group_permission(repo, user_group, permission_name)
+ self.user_group_repo_permission_ids.append((repo.repo_id, user_group.users_group_id))
return permission
- def grant_user_permission_to_user_group(
- self, target_user_group, user, permission_name):
- permission = UserGroupModel().grant_user_permission(
- target_user_group, user, permission_name)
- self.user_user_group_permission_ids.append(
- (target_user_group.users_group_id, user.user_id))
+ def grant_user_permission_to_user_group(self, target_user_group, user, permission_name):
+ permission = UserGroupModel().grant_user_permission(target_user_group, user, permission_name)
+ self.user_user_group_permission_ids.append((target_user_group.users_group_id, user.user_id))
return permission
- def grant_user_group_permission_to_user_group(
- self, target_user_group, user_group, permission_name):
- permission = UserGroupModel().grant_user_group_permission(
- target_user_group, user_group, permission_name)
- self.user_group_user_group_permission_ids.append(
- (target_user_group.users_group_id, user_group.users_group_id))
+ def grant_user_group_permission_to_user_group(self, target_user_group, user_group, permission_name):
+ permission = UserGroupModel().grant_user_group_permission(target_user_group, user_group, permission_name)
+ self.user_group_user_group_permission_ids.append((target_user_group.users_group_id, user_group.users_group_id))
return permission
def revoke_user_permission(self, user_name, permission_name):
@@ -1285,14 +1244,11 @@ class UserUtility(object):
"""
first_group = RepoGroup.get(first_group_id)
second_group = RepoGroup.get(second_group_id)
- first_group_parts = (
- len(first_group.group_name.split('/')) if first_group else 0)
- second_group_parts = (
- len(second_group.group_name.split('/')) if second_group else 0)
+ first_group_parts = len(first_group.group_name.split("/")) if first_group else 0
+ second_group_parts = len(second_group.group_name.split("/")) if second_group else 0
return cmp(second_group_parts, first_group_parts)
- sorted_repo_group_ids = sorted(
- self.repo_group_ids, key=functools.cmp_to_key(_repo_group_compare))
+ sorted_repo_group_ids = sorted(self.repo_group_ids, key=functools.cmp_to_key(_repo_group_compare))
for repo_group_id in sorted_repo_group_ids:
self.fixture.destroy_repo_group(repo_group_id)
@@ -1308,16 +1264,11 @@ class UserUtility(object):
"""
first_group = UserGroup.get(first_group_id)
second_group = UserGroup.get(second_group_id)
- first_group_parts = (
- len(first_group.users_group_name.split('/'))
- if first_group else 0)
- second_group_parts = (
- len(second_group.users_group_name.split('/'))
- if second_group else 0)
+ first_group_parts = len(first_group.users_group_name.split("/")) if first_group else 0
+ second_group_parts = len(second_group.users_group_name.split("/")) if second_group else 0
return cmp(second_group_parts, first_group_parts)
- sorted_user_group_ids = sorted(
- self.user_group_ids, key=functools.cmp_to_key(_user_group_compare))
+ sorted_user_group_ids = sorted(self.user_group_ids, key=functools.cmp_to_key(_user_group_compare))
for user_group_id in sorted_user_group_ids:
self.fixture.destroy_user_group(user_group_id)
@@ -1326,22 +1277,19 @@ class UserUtility(object):
self.fixture.destroy_user(user_id)
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def testrun():
return {
- 'uuid': uuid.uuid4(),
- 'start': datetime.datetime.utcnow().isoformat(),
- 'timestamp': int(time.time()),
+ "uuid": uuid.uuid4(),
+ "start": datetime.datetime.utcnow().isoformat(),
+ "timestamp": int(time.time()),
}
class AppenlightClient(object):
-
- url_template = '{url}?protocol_version=0.5'
+ url_template = "{url}?protocol_version=0.5"
- def __init__(
- self, url, api_key, add_server=True, add_timestamp=True,
- namespace=None, request=None, testrun=None):
+ def __init__(self, url, api_key, add_server=True, add_timestamp=True, namespace=None, request=None, testrun=None):
self.url = self.url_template.format(url=url)
self.api_key = api_key
self.add_server = add_server
@@ -1362,40 +1310,41 @@ class AppenlightClient(object):
def collect(self, data):
if self.add_server:
- data.setdefault('server', self.server)
+ data.setdefault("server", self.server)
if self.add_timestamp:
- data.setdefault('date', datetime.datetime.utcnow().isoformat())
+ data.setdefault("date", datetime.datetime.utcnow().isoformat())
if self.namespace:
- data.setdefault('namespace', self.namespace)
+ data.setdefault("namespace", self.namespace)
if self.request:
- data.setdefault('request', self.request)
+ data.setdefault("request", self.request)
self.stats.append(data)
def send_stats(self):
tags = [
- ('testrun', self.request),
- ('testrun.start', self.testrun['start']),
- ('testrun.timestamp', self.testrun['timestamp']),
- ('test', self.namespace),
+ ("testrun", self.request),
+ ("testrun.start", self.testrun["start"]),
+ ("testrun.timestamp", self.testrun["timestamp"]),
+ ("test", self.namespace),
]
for key, value in self.tags_before.items():
- tags.append((key + '.before', value))
+ tags.append((key + ".before", value))
try:
delta = self.tags_after[key] - value
- tags.append((key + '.delta', delta))
+ tags.append((key + ".delta", delta))
except Exception:
pass
for key, value in self.tags_after.items():
- tags.append((key + '.after', value))
- self.collect({
- 'message': "Collected tags",
- 'tags': tags,
- })
+ tags.append((key + ".after", value))
+ self.collect(
+ {
+ "message": "Collected tags",
+ "tags": tags,
+ }
+ )
response = requests.post(
self.url,
- headers={
- 'X-appenlight-api-key': self.api_key},
+ headers={"X-appenlight-api-key": self.api_key},
json=self.stats,
)
@@ -1403,7 +1352,7 @@ class AppenlightClient(object):
pprint.pprint(self.stats)
print(response.headers)
print(response.text)
- raise Exception('Sending to appenlight failed')
+ raise Exception("Sending to appenlight failed")
@pytest.fixture()
@@ -1454,9 +1403,8 @@ class SettingsUtility(object):
self.repo_rhodecode_ui_ids = []
self.repo_rhodecode_setting_ids = []
- def create_repo_rhodecode_ui(
- self, repo, section, value, key=None, active=True, cleanup=True):
- key = key or sha1_safe(f'{section}{value}{repo.repo_id}')
+ def create_repo_rhodecode_ui(self, repo, section, value, key=None, active=True, cleanup=True):
+ key = key or sha1_safe(f"{section}{value}{repo.repo_id}")
setting = RepoRhodeCodeUi()
setting.repository_id = repo.repo_id
@@ -1471,9 +1419,8 @@ class SettingsUtility(object):
self.repo_rhodecode_ui_ids.append(setting.ui_id)
return setting
- def create_rhodecode_ui(
- self, section, value, key=None, active=True, cleanup=True):
- key = key or sha1_safe(f'{section}{value}')
+ def create_rhodecode_ui(self, section, value, key=None, active=True, cleanup=True):
+ key = key or sha1_safe(f"{section}{value}")
setting = RhodeCodeUi()
setting.ui_section = section
@@ -1487,10 +1434,8 @@ class SettingsUtility(object):
self.rhodecode_ui_ids.append(setting.ui_id)
return setting
- def create_repo_rhodecode_setting(
- self, repo, name, value, type_, cleanup=True):
- setting = RepoRhodeCodeSetting(
- repo.repo_id, key=name, val=value, type=type_)
+ def create_repo_rhodecode_setting(self, repo, name, value, type_, cleanup=True):
+ setting = RepoRhodeCodeSetting(repo.repo_id, key=name, val=value, type=type_)
Session().add(setting)
Session().commit()
@@ -1530,13 +1475,12 @@ class SettingsUtility(object):
@pytest.fixture()
def no_notifications(request):
- notification_patcher = mock.patch(
- 'rhodecode.model.notification.NotificationModel.create')
+ notification_patcher = mock.patch("rhodecode.model.notification.NotificationModel.create")
notification_patcher.start()
request.addfinalizer(notification_patcher.stop)
-@pytest.fixture(scope='session')
+@pytest.fixture(scope="session")
def repeat(request):
"""
The number of repetitions is based on this fixture.
@@ -1544,7 +1488,7 @@ def repeat(request):
Slower calls may divide it by 10 or 100. It is chosen in a way so that the
tests are not too slow in our default test suite.
"""
- return request.config.getoption('--repeat')
+ return request.config.getoption("--repeat")
@pytest.fixture()
@@ -1562,42 +1506,17 @@ def context_stub():
@pytest.fixture()
-def request_stub():
- """
- Stub request object.
- """
- from rhodecode.lib.base import bootstrap_request
- request = bootstrap_request(scheme='https')
- return request
-
-
-@pytest.fixture()
-def config_stub(request, request_stub):
- """
- Set up pyramid.testing and return the Configurator.
- """
- from rhodecode.lib.base import bootstrap_config
- config = bootstrap_config(request=request_stub)
-
- @request.addfinalizer
- def cleanup():
- pyramid.testing.tearDown()
-
- return config
-
-
-@pytest.fixture()
def StubIntegrationType():
class _StubIntegrationType(IntegrationTypeBase):
- """ Test integration type class """
+ """Test integration type class"""
- key = 'test'
- display_name = 'Test integration type'
- description = 'A test integration type for testing'
+ key = "test"
+ display_name = "Test integration type"
+ description = "A test integration type for testing"
@classmethod
def icon(cls):
- return 'test_icon_html_image'
+ return "test_icon_html_image"
def __init__(self, settings):
super(_StubIntegrationType, self).__init__(settings)
@@ -1611,15 +1530,15 @@ def StubIntegrationType():
test_string_field = colander.SchemaNode(
colander.String(),
missing=colander.required,
- title='test string field',
+ title="test string field",
)
test_int_field = colander.SchemaNode(
colander.Int(),
- title='some integer setting',
+ title="some integer setting",
)
+
return SettingsSchema()
-
integration_type_registry.register_integration_type(_StubIntegrationType)
return _StubIntegrationType
@@ -1627,18 +1546,43 @@ def StubIntegrationType():
@pytest.fixture()
def stub_integration_settings():
return {
- 'test_string_field': 'some data',
- 'test_int_field': 100,
+ "test_string_field": "some data",
+ "test_int_field": 100,
}
@pytest.fixture()
-def repo_integration_stub(request, repo_stub, StubIntegrationType,
- stub_integration_settings):
+def repo_integration_stub(request, repo_stub, StubIntegrationType, stub_integration_settings):
+ repo_id = repo_stub.repo_id
integration = IntegrationModel().create(
- StubIntegrationType, settings=stub_integration_settings, enabled=True,
- name='test repo integration',
- repo=repo_stub, repo_group=None, child_repos_only=None)
+ StubIntegrationType,
+ settings=stub_integration_settings,
+ enabled=True,
+ name="test repo integration",
+ repo=repo_stub,
+ repo_group=None,
+ child_repos_only=None,
+ )
+
+ @request.addfinalizer
+ def cleanup():
+ IntegrationModel().delete(integration)
+ RepoModel().delete(repo_id)
+
+ return integration
+
+
+@pytest.fixture()
+def repogroup_integration_stub(request, test_repo_group, StubIntegrationType, stub_integration_settings):
+ integration = IntegrationModel().create(
+ StubIntegrationType,
+ settings=stub_integration_settings,
+ enabled=True,
+ name="test repogroup integration",
+ repo=None,
+ repo_group=test_repo_group,
+ child_repos_only=True,
+ )
@request.addfinalizer
def cleanup():
@@ -1648,12 +1592,16 @@ def repo_integration_stub(request, repo_
@pytest.fixture()
-def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
- stub_integration_settings):
+def repogroup_recursive_integration_stub(request, test_repo_group, StubIntegrationType, stub_integration_settings):
integration = IntegrationModel().create(
- StubIntegrationType, settings=stub_integration_settings, enabled=True,
- name='test repogroup integration',
- repo=None, repo_group=test_repo_group, child_repos_only=True)
+ StubIntegrationType,
+ settings=stub_integration_settings,
+ enabled=True,
+ name="test recursive repogroup integration",
+ repo=None,
+ repo_group=test_repo_group,
+ child_repos_only=False,
+ )
@request.addfinalizer
def cleanup():
@@ -1663,12 +1611,16 @@ def repogroup_integration_stub(request,
@pytest.fixture()
-def repogroup_recursive_integration_stub(request, test_repo_group,
- StubIntegrationType, stub_integration_settings):
+def global_integration_stub(request, StubIntegrationType, stub_integration_settings):
integration = IntegrationModel().create(
- StubIntegrationType, settings=stub_integration_settings, enabled=True,
- name='test recursive repogroup integration',
- repo=None, repo_group=test_repo_group, child_repos_only=False)
+ StubIntegrationType,
+ settings=stub_integration_settings,
+ enabled=True,
+ name="test global integration",
+ repo=None,
+ repo_group=None,
+ child_repos_only=None,
+ )
@request.addfinalizer
def cleanup():
@@ -1678,27 +1630,16 @@ def repogroup_recursive_integration_stub
@pytest.fixture()
-def global_integration_stub(request, StubIntegrationType,
- stub_integration_settings):
+def root_repos_integration_stub(request, StubIntegrationType, stub_integration_settings):
integration = IntegrationModel().create(
- StubIntegrationType, settings=stub_integration_settings, enabled=True,
- name='test global integration',
- repo=None, repo_group=None, child_repos_only=None)
-
- @request.addfinalizer
- def cleanup():
- IntegrationModel().delete(integration)
-
- return integration
-
-
-@pytest.fixture()
-def root_repos_integration_stub(request, StubIntegrationType,
- stub_integration_settings):
- integration = IntegrationModel().create(
- StubIntegrationType, settings=stub_integration_settings, enabled=True,
- name='test global integration',
- repo=None, repo_group=None, child_repos_only=True)
+ StubIntegrationType,
+ settings=stub_integration_settings,
+ enabled=True,
+ name="test global integration",
+ repo=None,
+ repo_group=None,
+ child_repos_only=True,
+ )
@request.addfinalizer
def cleanup():
@@ -1710,8 +1651,8 @@ def root_repos_integration_stub(request,
@pytest.fixture()
def local_dt_to_utc():
def _factory(dt):
- return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(
- dateutil.tz.tzutc()).replace(tzinfo=None)
+ return dt.replace(tzinfo=dateutil.tz.tzlocal()).astimezone(dateutil.tz.tzutc()).replace(tzinfo=None)
+
return _factory
@@ -1724,7 +1665,7 @@ def disable_anonymous_user(request, base
set_anonymous_access(True)
-@pytest.fixture(scope='module')
+@pytest.fixture(scope="module")
def rc_fixture(request):
return Fixture()
@@ -1734,9 +1675,9 @@ def repo_groups(request):
fixture = Fixture()
session = Session()
- zombie_group = fixture.create_repo_group('zombie')
- parent_group = fixture.create_repo_group('parent')
- child_group = fixture.create_repo_group('parent/child')
+ zombie_group = fixture.create_repo_group("zombie")
+ parent_group = fixture.create_repo_group("parent")
+ child_group = fixture.create_repo_group("parent/child")
groups_in_db = session.query(RepoGroup).all()
assert len(groups_in_db) == 3
assert child_group.group_parent_id == parent_group.group_id
diff --git a/rhodecode/tests/fixtures/function_scoped_baseapp.py b/rhodecode/tests/fixtures/function_scoped_baseapp.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/tests/fixtures/function_scoped_baseapp.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010-2024 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# 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 Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import pytest
+from rhodecode.tests.utils import CustomTestApp
+from rhodecode.tests.fixtures.fixture_utils import plain_http_environ, plain_config_stub, plain_request_stub
+
+
+@pytest.fixture(scope='function')
+def request_stub():
+ return plain_request_stub()
+
+
+@pytest.fixture(scope='function')
+def config_stub(request, request_stub):
+ return plain_config_stub(request, request_stub)
+
+
+@pytest.fixture(scope='function')
+def http_environ():
+ """
+ HTTP extra environ keys.
+
+ Used by the test application and as well for setting up the pylons
+ environment. In the case of the fixture "app" it should be possible
+ to override this for a specific test case.
+ """
+ return plain_http_environ()
+
+
+@pytest.fixture(scope='function')
+def app(request, config_stub, http_environ, baseapp):
+ app = CustomTestApp(baseapp, extra_environ=http_environ)
+ if request.cls:
+ # inject app into a class that uses this fixtures
+ request.cls.app = app
+ return app
diff --git a/rhodecode/tests/fixtures/hg_diff_mod_single_binary_file.diff b/rhodecode/tests/fixtures/hg_diff_mod_single_binary_file.diff
deleted file mode 100644
--- a/rhodecode/tests/fixtures/hg_diff_mod_single_binary_file.diff
+++ /dev/null
@@ -1,1094 +0,0 @@
-diff --git a/US Warszawa.jpg b/US Warszawa.jpg
-index 5a4b530a9322aad2b13d39a423a6a93146a09a29..b3b889566812a77f52376379e96d32d3db117150
-GIT binary patch
-literal 85172
-zc$~C)2S5|s+ThHjCnN+2EtG^_0tAwf03w7A0)m1FNTf(87CIILK}Aupi-NuPMpaNy
-zR4!NS9eeKuyCO>1Gn3%OckkPM|J&UpXXZO~&iVTHO)SEF{q3C`L{y8D^G?n@S$eXH
-zPIVG+9l4oF`ouhW4wvsgF)>f>D<7AcEay@^Jh-`e
-zIr79w)TwJ5V}ml2ota0UkM1FteYo-TOVF0Uy9a(yOdPg!(p|XtI8FaRvz{MQcYXZy
-z)veR6d~f&Hk^8^oHXC**5=X{#uOb&GG|3;Ft+ZnSif~Me8^>
-zZohqef5Sbf{!UGq=c*8%MTCFO)0xv=HY0x+ub(l)s&vUFuQXec(Q3-MM>}Ze?$=8m
-z7#<3a*3z}xw9b3T;}gye!D(F=#pd`v(^ZmXIfC#^{*M&`wZNi&-t?Xi8K&hAh@x`S
-z{Ad2F*Uvp?cbdESbN2F!D}N5{EbKJxw7y=`Q`pc9b+=u7mi@TtPiF_pq>7yU25um&
-z$#p00aK@!VxFXLhb;k157f$hZ#XU^bE1UT)_*nBP!&ye=zSsG<`GTyr-BaRbnT@Wy
-z+P&&hcI&E7>idNSYjr2pKJs|eo2A=ld?=W4z+H0LzxdeCC&gN=N6My!Pd`#QmzP>C
-zoJ*5$`1JM3%NKWajP4|lxYvdhF1>D1e9gL}yma`HA6p4kGmdNT(t1kw+;V60hBr2o
-zUkyLS+Pc?$)Y*gZR_T5(_g5`)Q)uU-z0RG5`$Y6RymMH)-8BA)QdX!ZXYumDgX0{07THaWT6ptt{y5@8n?=o?Ggc<-G5Dl^czbw|
-zrt6t1*=8Xe74UKa6xB9?Q*p!MfGi$wJSQ}OdHVJ7dz&j>8_pnhCz?Qe(s-qNv@W}+
-z_NMPY-*CC7fLnX*PMVD1!F6sjrDon;bZW%akVhkp!^QAMn*4?!llstk#`e46&)SRe
-zmm04GRTicHsPuJvmXK;B-S=kgPeGdZ2&wOVmxWQmD^2#v0&fQ?h#3TR^Bct>v}XzP
-zFP>PhXZO&ZeD~_7Qyxz!i}*9Q6nJ;f*SZ&wleqHQtTCgOZ(9Fn%^J$|qBLpC>@D1g
-zQ`uLm9$wnzUB2&x@s*8pPOkE}Fz?lZJokrZdevgJqdax@Gj%dgYQ4SuaJli@vG2XZ
-zviwt9!fg-Y3S%wD5o^zdzMtQl+H^hXUR<`3bdtWP)DRAM`7=m7!YG_FbgtArzw&0-
-z!_Rj`&ws*W%*yr3Q+?ISYZ$?14qIKFyeFOh)^Un8uVRAEp$Cti&ReHb;9=JEW9p0~
-z%jCd~g<21b!n^7ZJH9iUzM*$_v{flBggp9@)l}!EDQOejA02MXzPTy*-ouNzhWiDh
-z8t%D&+V*HHi}YEm|N1KU>Ez<5yDhVRuItrMdkW318y&u$r{nN?)yr>tr1e*lU5d7D
-zU#mF$Jmq0Q!~~(?{9v!pwdvtoq^pf5W_4J*eOo!7G4?&{(<|2EskzOQR+$bP$8LN>
-zn&8&Eaa8_+yVp&udNWqUg%&qozEl>nF?m?t$egVkx<*}|bD%DA#oHx&8xx6RHiS;y
-zJ&HxX%iKyx-|#X1RMV22tU0^xcb&PF>9_Jj+i|b?c4wnrwma$t*%n{eFzz$?!kQaF
-z$NoI!^iVN=;=1ba3wt*2XiRpwsKd=|I*{nHDx7|_m0m90r*Zh|=r3KB^j1IPcAeO9
-z>MQ0u7vSDpiI;1BG@UTb<#wm})I@&F3a#(^8%e9*fOQz`zwtvkF3i5>AQJ|YhCT;WR!u5M*
-z7aZ@(t$m)KeI<6j=;sOY{I<$F-Jfu{P5bqB=H8K6H1dZ>UY@WiHfv+mz20HDlU`m#
-zPQNR|`>&ncwtvZ{ksVLBDJ*rzA}#y9T->c|Q=Dv*t$jomU-&|I=)p-;YWWuAP+
-zjKyk>_d6apsGqpeJK0yVzBD{~G*QyI)X8ng200{Rfx7$9BKGI_8>V1C4kw
-z|Gac)^vR1KSmW2OD=?0Kad_d^E9)9ud89y3u`4Lce}HEk1h8K1t-z366Kwi`^BsTdBT#Zr8MR
-zT7Q~y3_rT>=)SVsGYVYVx7i$UH(AoQF=$xO{E*RWa_5HF#GD-Vto+Lw=^b@
-zG@yu~%$meWjtk)#p3g0ZFPt5o6iB|f;y6v+Amz5H?x@P~g+zZ@$8Pj#HO*b8J>^vK}B{ucMfqgO8mVOi-*&KP~N$;DZGdyh;-BlZ!UO9_s+j!z$neMp}BXY}X
-zu0<_@KWtwEA8mh1dOm-;e5Peam}`dZ+ij^M?&M6>CY$JokD3;=O=D=pgIJsL{hKRZ
-ztiL_PY5wBU5i`1eDxSF0hOBPbG$UNwb-0H00b#^VgF>y!&R5r5#v~=ZpEcd5E=(#ALfQH
-z7n=3$Y5(YKtutYjEVOglh1m^70o&@6>OQwC>_jaO`b{rS^YV(=fpd!a&RKoHQlBc%
-zNLIN407BgdC&xj4gtvF*RPGqGMc@cN%Bu`@DKc{=MNZ3-bDjR3f5FGP`O#rX
-zxW9Z7Cl>%m*RaS*u7iAUr
-zFkF6kPUg4>c^-ERA9zJvex!VA9+w|9DKS;lbN4|E$6m?O^^H%&DyG%+tH9pJ->i(&-3%6@mGu3R|lTx7@f$@zoXY>5O)6=Z==(bQP<7M`QPJE=r|aM*x5jXcY0oKxID)vb5d4jhCCyW
-zi(=X*b7E#rL{{QBFwj>%IenZQ*u`m}BT$~6nwG~E34TAp+v6|{LC~y?{d*?NYlBoD
-z?z^I~=BNIC8rT2PX(B=sb0-YY?(c^S$^i4TGSTVWyqtXbU*{DF#e)Cl#Q#9re}C|}
-zBa7nvm9;a6vcO5f(bOgq)h6&cR}O)J52SRghwO!Iuk}>Jr-v#XZ^tnc9&z
-zBRjv;KbM}0e^>mu=iH$~ytMxN0bz4xlXpGoy;lWwZt1#rWB(iSoo!}qL}r~00)I@b
-zlHHoMaB2h_f1>uT`jI!6R}#3DYYx3Tab&IU_L3#qdT|Mv@|_jchu(!e(%4Nu@9ma>J-PCTu2*`djqx$=YBbnXtW2a90_*~oFZDkZq9ygm`CY!v<
-z^8L+{)(gixNl)HODnIc(9>=pHahAC>={qpF&R?2+;&$c7_ltMb&S@}vXYj$H{>iI%
-zyzo1|_&U6YS_qkaXYupNt1M{JdA;4Hhr`Ij>>gLiCch-7O@hg=tdVP3=xmm>H{eFi
-z=auoHGi@H!pL5$3-By|y(D97+#%Z~*_R8?4eXetodKMh}>3CMR*r-@nZ-+(NE5+STCeeJWdzU#h^*BRPs
-zq}*jfwIF>Cf7`ywZzfGe@7?CUG`RAX_SR5MaOV7(>a3ePDQdfKA==zm;}Va1J|9Wn
-z?ko{>xopc?HT8^j+sQkVHxes1MfLDrmB?LoY^sRz3jI8zta7Jo{=WB5KkHooQEES8
-zCscR4*y6Yr>gM*R~po33lm%-Lf*WX{u~M}~p7
-zZnezXx%i2`Wl`UwSi9Qw<6Qx=+ppvoo8v+n*=fYKHQA;
-zzMRo`AV1)J<)RHIn+#<;kT;LRHc<{6bVnxOH|%{`@u)P!T4J|6*LtV$^ftJ3CeJc<
-zTm-Xs(T3Y3^9JU|X|7{8HRKgI-S8N8Y5SVA3a9hSpc60o`xh^=c(7sq`m-#{tLD4U
-z&99J&^ekUIIig*(NeB9wPAfeb%Dr=WlFq23YTU{rjm}z|?;LA675e?_RenTu_>!*+
-z^UM1$e3>`pZtH!dH0gvOgUXG&KK=d~_O{bwR*p$Ik(K6sT;e)0?rbSyqLp1xyP+g0
-zt#jIxKaZZ&R5NtzZ7{7+Oukdh&gMFiCC%YXg
-zJe0Y!t+aX8vbLE&tDF)m)QY|zNcNueXPTCLbLy$p7vX0g=e!IF+#@()abkpF%COdg
-zITJ5tW)h#`LB9eyyzj2bjz`|Q{;`(7_^
-zp0%`c+1jy*`dZYom{m&*VlI?kygs{ZBx`WoC<@K%LC=gti5+Z#IlL)zpid1FQphuDo5ou_&@tk>+_
-z_=GdeqHC4(4>Lg)w&A}{=EI?!@ce%Wsk?X?m3bCLH#wUYL}s6
-z=ji)zVg==CM=UbN?c5qpHd>%bp$Tu-jVaWt4j<(PxZbR%R>?n;?!&@je&p
-zIo5-swer%iw!|LyDoWYoN#`XOk1x}9yGwgnA?JRcm^;I5^9qw@cZ2J*!+#dcBz8$j(umopjhU-tFG{IW&*6VDPJ*%Xe0-
-zT}g>)xpFx;zN6H~)Mr0`hgYJFFtPi@+n85ZHZ1)z?Zl-w-8--7<ae`-WOcIt#UL!-s^H_9_7qQ`J!2WW>t8(82&Jx
-z<)!z)T8qe;?m#ZPE%&~)iDPq$Jmd7L=dD`HNS1LmMiX30r(|u2O%$vi>WW;jx_`7g
-z@a4kRbr50QX+mQ;CWS#SwHLK
-z+v@j`+=)gJxCY#%CBHOToYHV-{odu0?Ip>^K`*Mv
-zGh$>%on@8j6FSnmO=eoU%RhrXNnD+DNgC*U0D-QW4CuTm$%KbL`Nb;(m
-zpQlmQ>{=e?>K{sf8+8w7^X2UAJnl)yo$u#KsHa&Q)MM(kc7OPCJ?jp4i|5a2*^57X
-zM8e$@V>(JhY>nTv$M>H-vob9Bn@a=Nn0(%ZA}m>WTrj%m;>?mMQ55aMEuHgizB({d
-zzOcL}zj+XJ?0R;!Lr3z|GZ`6LBPZ-yHYTL&WE`h;%=p)A+oop|SMDh9UDY3Sx+XC1
-zapjytL!v+OyT2Euc~vF%93FP_RKl_7t9Qq_jxOZA-uTYvv9+s4dk00&!|z9dg=q7h
-zGkceBc(vk^*^bw1Hf+y{W4rK&Pw$G$-lt*ISH+hcOQ6Soz`b*OwLz3}aryPhef=b|
-zqWtFHokH;-r!X9Kp!2wr0iW-8M}Ls3hE{#$DT(O
-z|CPZ2R(Ik$i3Zn!V=h+J1LYGZbDf<3+gRa0k3;yrAufsF!A@9}pO*?cMO-YBQa$+}
-z;*Nn`2PvIhez5BP{NnSq@kFy`l2t^u%AGnX{|2gFxuQbrHD8F#i|$(HVKUTr4Au
-zVjZlYB8}wlq!}2*4^B_c<&ME{C;RPzx!9ulV64;NwFc*#m2)Nk@m!}t8yVE<^!F6D
-zlat{8G28q*Mi>YVl`*fqGT^lz6!#8_e*ZDl`In$}aBwkd*b(Y%Df7)c-ZN5n-Jya*
-z)Q8mBIxkD#wny<+l>YRL3DurEO)zy+#GSe0`ltHiWK!LGyhqxmuf%h=hWw?sMqn
-za2C(>)x4g>^$Sv;BNt4)G~b+fdF)wItbEJbu`_O~6}fF3U3RGmF3L5U^mmaI3=Zmj
-zRa6FOh>nTniX6Eju@g5Vf8s>hZ!{Vl+zn5h1du<*dO~JKx_q1sSKv4hPYs$dIJWhb
-z=b|j=`ZpFFOyj6@`4o9hc#b?pj`BKqg3~We5tg4fF+Bq`34f0leG>B$CuXK{2lnLj
-zE1pF8EfggMe^jn~I0y(;ZModP3xY_si+_p`RYRQr;eCqO5#`3=U{C+STNRi?eeh-K
-z+I1^aBO|+X{=CpSrC`%J$2`4)caxx(tw9Gi3(l%JR>Eca%s=MNqx!Nm(#yGCFwWm+
-zX|%>4-r_N?f6ksHl{L>d`LD&aNx#)?4Ht{oA|DAkAZ$>>v0mFCV-M
-zTD_uE7lH^CHBes#OLdcN)lO(<*YR=&$anod>}tv#qYHTl`c=AcgGuyOE)jEvMjH~z@Vx$J_>
-z%ywM9;`EdHm)-g=SA~pqfHveR%=NP;z1(biyZZLc8+v1^tBXhM>~HSTwdj7ETA=@N
-zS1NPTgDJN+e3y%$wrZ_wDbL>>EVYFWAB4EY@20G|4nyXCMXy`F`gw>xw3yTItG;gE
-z?&|7l8OJ!|8+Y;k=VBwptfM{a=TALy|FqAy2j+q~>B!-xjJ*pQ)J!(4U(uPI{i37T
-z@KLd0&-3Sw)Tib-J%;P8O-G94abuV5yz4F=-FEK5rRJ9Y>+QvQUr9ylCoHvI$bs{>
-zQI;I`C!D%~gZxYo^NeF@gx-Cba8Drj=(G{zmv4BkSiZ6Uj<$ogXI)d!hDUdNPQJ7=
-z@%-^=bx$lJ;zB`E0#&M=h73(mvn#RBf*jhkdS-H&jHEVd8`8-&dhf+Jg%E~8h$#vTv{8xC;asYi=E@I
-zCPrHAR?pXRp1azw$andr(`h~pLvQICEgbIsd?W3W_6y5AQZG`VH+1HVg~t=p^kfU{
-zJ4jlygO2b0qcY)Z;w`6CC!gxMzvnimQf}4?NNz~6D}lVCRo*EX8EpV
-zpGTh0srchV*7qiA9Iac_P0Y|dR8nr6JPqq(lMZDz+>E#X0c}wyR=grTn_$+^EqrZ2-q6ItD
-z#%z~9+jw@`s%)zFrL9d7(Us>a{ZA84(dU?a
-zc|Iq9Q$cI*#@<qs4hNbQqUUj~5f6M*@-R!$jcS!}wLsMHv70?R~eRk?q>x=9w>g}Q@;3mvY
-zp3R;;VQ$T)g(h8D8y+svxOM9Myws}+dAF;R-;7^7>CSlf6mKELu~YI=FfC)ZoIT<2
-znTGqHx2$T6SskKzwuIH!1PsX3(?bYm}b)0n$*>$cQyVAh!h<$vter)#MuVe1k
-zZjUC#W;S*=DiX#`IC&_&e$~Nibqf#eYivEYQ7}v2VnOiav>}os^vPCl@7=n6gZ5_e
-z&HhITZ5&icc`E6vBh*H&z&-QLzI
-zzdY)`^B-U4z0Q>gB}d1U-EKM3`ory8z45%VLjgdaUj4O!bxH
-z`A_ZNQW3ACUPopd6JHY#P!%3{EltCbS|81?x{LS?o(nV&3ievO$!{7nHNB^g+)7UL
-z&D@%t9G9H?q_8p|GUjSpL;B~#FZMWP2<*N|zN+svCx+yWMncHk~&2_4$3zjs|`R+7-Mij6Q5=<>6|b71!4<3SJ!M
-zd3^c9Z6A9Dk;14{H90#)CuPz8y7&BgpdmCG)y_&Y>W5@O8{d?lo_k~5@KjM|^
-zbA0anh4&29GfgKByZhPaXv~Zl#h!B`)=Z~HQpGw8GC$nA9BVz|6u+hQTH=Je;;Z8B
-zgU8>lyuIgOv0aj`%tp-HZ#^Uagl+T372ij%h_~+!yyFx-KH_k(Rf<*S^HUDbWo2i!
-zp1EGR@EzmM-I(EgzHM=1>eKaSw4=21r&FfSnI6Ey*&f-`uzPY;>w)3>jrV8oNk1@Z
-z|J|`ejxBC|*D<52Z&JQ({vY}87+LCfc75CbZsNuCoi6iStBc+TjY}Ta-?iYtnl-F7
-zy&M|>vY%0>gVv!UtK=u6=r_y+qC?Z
-z)r8@l>uraIy2!E$KAc@G%uN+uIKMX7O>*x@-8aSe7oQIGdiN6fJ@HL>mtH^mKBG$^
-ztTN=UYnfR5y&acy=dM&+cl2T5<2#{?
-z*XU+`zjxTJC2wk)dD4^fZ<}s!nQ~*w_U`R>i?aDY?w#mcsD4qs+<4f~y;HaT-1#t|
-zk8?XHucI@47FO21G;<>9ja?Ql`b=7XJZ=H+!Ue@i&tzc+xq-|-=mLb+9rN}(@s-V{1G2K*0#m{^XXTk-o?GYv!(0h0o`X6_pDo7
-zf3Ey+uwQodNavij-)?+c!TId@x$tv;Pl8iX_J{ayrJsJBNuqfD-CY;{Q*gT$WO3_m0lL@FC>3
-z-8_#s(+_wK>59p}VO%KL{btOIHCqg>HGi6vbx2{%-Z8xU^0dZ_n{Vyc*XkIZyN}dZ
-z{dn=>+b{N$io35RHIBUbExkuPx2)h+Ltkgn$eUdk4`2LVQP3M>WaV+M?BSs!Zl1fh
-z#U2>-`h4#03t!%EJL;=VzYx3c
-z>y`Q_+Vk7}snHdut~7riRkoPi0)nBad78BseQ~08!yfMjjo@HA$HQhT+eE!cq1jQ>
-z$v$@y^1=#n+eeM6j?+A(S7gzha`jAP+TszzufKP;+xn0fdGWoy&CZ8op858yjGH_6
-zK159S#>nV4
-z3At;;UwGq4Zr9{{t?_m~p6xD*pYWpfr04$-<_(U?N2A#R@Dqbr%~>GAf;?wu$zMev
-z=89ZhxFV5=>nsw0HUU=x?gD`eHXf}DorPSXivWxfbA>_?S15Mmx;i>xZD>C>M*`M&
-zktpYZb*jce@%&?dLx^22hHLsWIdoSyye1;R6J*wP^
-zv#Tq*V+q$uD0Bg92t~go3Iw8m2(29jE{?z6_&N>L|I%U*`dXR-IYKxnVN&AMEJ6|b
-zVj8}RzMW2!CnnpsYClAZXlf(bA3Ftt&4AD=8CJwV
-z5RLmo)LTGpRJH&0k&rk9_%m;fxN+1Yt
-z4(Xxo_-T+kek>FL$|U3k6btT;K=A>k5I+_^j`m}xl|Tz1CX@`30aloh6dDCk8Afym
-zbVh0;Aumn12n=I_s}z(apx_`+I3J3~%4ATJ!kP8f_F`!hoLQ%9AZ~)Q>v3)-OT5@{
-zHkj!MuHm3$fwBRVW1!pzg$SvGt0yS&pcI3G?{9-6>I{v<%>Wozr>TVj!|Q}16<}@&
-z79U>kuESApg~veeKtaIMW9#+V=+k5CwY9N(9g~U8h^ePg(S|J5_Oj6Jp&hb1Z6%OY
-z$I?~-s^^Xz08;DtVimx!#DJ7K3LOIy2LO4UP^<#1ZB>8~7i>~q$JXa4hlY(%&GGP5
-z0s6`ZB-U$T+fA&e(b0Q+owWe#2&;>VRRZ;{L&T0=I&c^`Wf;I>7{GQGxR!u|0Pal&
-zbV&x#MR0w9LU3|jRxF(&E;gs+8hj1hI9+g7
-z7ks+YmUFt()#WryJLP){Y3gq~c!#1P4!aBkP-v|xOLLH@VczJlDds`HFX#3
-z9@J6W>fYC(qoux$;6NN-SUrjpg@lcYABDIav*lzSgEt&Ia14<&YjgNV;FhDhoYtc{
-zoTIJUoWso{IR~3fTMR4(g4GWNxjWCoHp466vlO|>*BFr(|F-ykan&3R?A(nuX
-z#tP)rgJRFp;MlP&I6Rgs$CjnVxo`c>8o6MN-f-3|CTBg@hr?kRb4~Zgo>d9&@t!&^bYEWs8ouCqmHQKkPFBKTtCu}!>iF<33yi=;tE5q;RLt|
-z%&yT4hJqu#YqVf+kMORg2zbFY$s@hzEL><*vod&ObWMp-OcNQ5iLQZb;6*Vt@J?obSF5n>8
-zX!HS)bZF!dH&+*l*jXeLI63ki?Cp5AHr7^_7UpKACR~oOk)Z)wUr$#@o28|x!Bl6^
-z)o4@-nM5SuaR?0A!8Lk5(Y5*{o`H#pNsPU!&i=h#gR@|MG^v3!e;Z`*FOv-arEc^u
-zb>m<4;WZGe#^21x7hSj(@_$wXY1P228VG#~tQ88Lq?+WHJl@PNy++?BIUxbe@HJy{
-zYy98yRa?QN&_GbKwxZEE$r_BV*ujs=T?hAWV*dwjV7-KvG7C7
-z_!?!AhV<>#rUlYzqc=L15Ydq@{%58lq1P341O#y+)c?166Qq?b^y0G%=y1
-z1fE*5S#n|=z+58OV=aWU@JowC@#u$^gfwmq9xRSs3_uIlFO6GnhN8nSEdgB3e8H@L
-zYzNI0pXf4^HiH^XaQCax;MD|x5dj5H3~=Rsdg)xWT3%kpt=TvtdT_4^dX0&R(E}SU
-z_cH?vg2nvCdxN9s@$K!DXHp>`Ibl5d%<+lnKK;gX%PZvAUKe0{z=ryz0m3BypGK9J
-z`z4$CB_}3(D_8fakw#)SC^9x0J0L)vZ;Yx*H30PBv7Q9q7!xJ3fx!Eq8#7DvHBgfM
-zS4)Db1vK~#baK%h1c3!=xZ}7r(8y>rFw_OT$X%fFaV{A1n8d(fR`_4DY6uogGj918
-zs0L0jd;8}1mPAzx(SrE}LP38s|Ag{#e>1Lsc|v(&Q-4X48JB5RUK<=-p5>PSo*E9+
-z5okYLVNm10Af|?ykOsQ}+@l=eKQh|Wz(gZvpc+00kpNcSQAPrc>jG%3LK})Bxyvu~_^3b=6O=nEnxs$?sMM56~^;2i+X!)hN)xbLcn{K(X
-zzedX^8fSpSC?UiEheA{ykaYJNT^@j~dF6lz7tLyzyc$Aun}K@_m#G0X07|_Gv)~c2
-z(OkdsUtFMUQJoa!1HkHLiD~63Q9_yipIcG$p~N)Gu|T9N0LDv_#sj7Szr+Qo`Y|bI
-z*7$!lF)%6DFw^9^^3km#K9SLbpE{tJfWq;vHG}7ksFlL=B4VSPnILeQ7a3iLz=%(R
-zcTBB0=!vyt6kd_I(qTZ?l7eJ#*XC<&ep
-zlfi6P=peivdIZISZx(PiAHgR9_Q6bi3zP=9tp$IJq(^!K#X}=N*$p4UA0%9ZWC#H7=~RZUHe2(IV?bUKp&0hM1*
-z(U5@qDJhLcBMyNe6MYpAP6GAzgXlOyGMD)obu9Y?8bUsg
-zr|Ig-#~e-BTgcPh^Q|VRf{T+QT*fTu(`$oyGDv1(0-I@=X_yFfbajAFNhE2BEUhdp
-z&CE1}0^p71obB;F6Mk!-tRAzBt9f(KaihegCL=jyxMAysw6E$xtX*pTG5v
-zGrZkP&NbBSJ8#Ul%|kKi*8dP!kDm&uL1K7$EfLkM&5&CEs|Ix6;DE2B*s&p)PSxTJ
-z#iA)A6dOz!20S~_Z;ee1lEGZL~SkI_)!T-p~)IUHQ9zDzC&4X
-z-WErm3EA9xgpRJdmBlf2b927Kvh+SYn$;W9{~WgnKLxUa)s%cA74w;%OoXYf>#2q}
-zHR75$njj`do)AUd)YQhBP1Hn5Pt-w4Pc%eHPsB)11RR2qo=CK@{+;wSv|yASP5WQD
-z#Iyl4os((0dcJi%Cv$Q%kTQnoI~j!POATU>QBTwx-8?nr#tS3xL=`LP#7TA2lfrZ
-zeI>FW9hj@40iDJqdu!ntFifVCfduq4qL_w&;67+T@ehC=Ott~=xdH71z!NDIB>)WT
-z=`z&@=BQz6kcN_rPQ+ASQ~zmIujrK&hjJ)7H_Rtc_#R
-zRokJH5mc7wC^``~q{C!l5~^rGag8Y&8YZ+vbO2V@K=BV{>M?0#w2j6F8~7^KM<>z|
-zOwzHjbOh7c8M>H2p%SGe5)dO)r+Q-gR2E}mrtwQufHoCsVC2!peQip^%Hn($8z-eWfw^c0B9ht~l=jZpRn?1Gf|Z
-zJ-7G%&$*raf6MIz!0qraZioNBa=YL^;&$!-fZOr1LX6pgS1_di86EYr`6_kGoXbJU8X!c|#ptp4K_K6-}Lq{1K92X=`WCvzrj0lq$R>
-zupuT|+zlZ2#941~k
-zHrUyFsTN8`0w!JrJW4u3h8iYMfN&Z@8D9qI=IV+G7I4)ux4f;b;ENu<4M2V5Hb@41
-zG@dLQ$Q!J*DzFg7TYgi-4{K?G55H14BL;yAdm`&+Hhe>24>sWhbXW?oBNS-7R6m^r
-z<49z31A=3LGazISm`uWX5C{~M@8~GnD0m7EPsSl6(9(!DkVvTWN?}PzG+jeCg=dT
-zEafV>LXU{aHj#z-dzfq!^)RpRFMm&8@0UDlsP4>jsH+bgeUu|a`cll5mZGE?!Vob<
-ztRXtV3}2=%D_95H8Q39J%>W`}`qfmWTptV=vREmItOU%+!$?jX(0HE3rEaL%xoC%qkDn%B;iT-ly@EpbaM=up$V6BIbVCISS%a(80
-zuzcwT#6j^)aSk2|GhsbgtT>~1e)Gzeo0o51LvcP2;M@sFo*HBX4^`n@lcW3s8m*2WwQZA>n;GmKF>pn;Og`R4t$
-zL!d+fRYXclr*tb&5?NZ22An)iTxLO8=sIaYhWC=KClp^#jhX4GR?`x&J-zi7A~zKnoEjS-jKn4qh-3;4LI5&|1Ok~t1!zGNCQ>FvTLM80bq0sw
-z$w*+}iF_=I0y_nJ*3eaI*FNxrWJ*bc&Cs7v3#e2r!ezzN{fc*r5Bn64+W&wlYhdPK
-zyzIvoob2ZoyzJ)&Tq0V4OcevNjUwDe^GYyJB~uiFFA;|TM;PGyr_>TfCmtdoSbm0z
-z*(zn0hKNcGY6rj-LMfoqa({W1pQXB(TS>+odTbV~rgAHlbb?_D`7c@G@j8@Rtpjrx
-zF{L(mH@ywO91lSOsU^z#_zeUEZ6RR(H6a1Q1Oj4*!8LFjd=bXOSn5e#0`5OC7X^(f
-zP-hB2n7F{0mk49V4Bbl@1qRlnt&XNH;mLk&j3;}``;3axz_w6_Q7AEU7Q%roE%fWx
-z@v{S9={gL4h@4YHbV}7r#gv#c8U+ba+&O24{8KuGfu)SFNvdR#rYh{ZgC*7Uvw{aS^`OLx
-zySso7>JYzAz{m8(pahFD!}4FIR)~WGc`yMT{Xa{$fEB%?O}w-y<|9m}Z>LfIr`Z>_
-z*kDAs7HvJ1R#|Q0&O!ZkMGWoQOyMg2jgxo*ID!zcenpa&!HMwiDk5)
-zT<+nF83_-fi0rQBZV*EBBL}G!!9`9KsSxJtG1MhNM({uFP+b6nP5ug=*lasC%I9|I
-zUpri|Y#zqpc9^xc%XAh9NCsTYq;oL~!^KQG7c=QxY!Ej~G!P>R`M*L`ciHcomhn`<
-zDy%UmTLbG1#HwPkGZw@m8oyFI5}^hYL0QHbXYD^_$l|R<3xd}4b!ZLai=suNrX)rQ=Ui#S_I>woW^s?kAwr9sw(a=E}{bbT|qj5TgiW5@b&V;{OQq0bxGRz0ZBX
-zQ$bB1DDb0q;DeLGMnAT|MnFAJR3sxEz~j({f~3wQDV-^S(F=(Qq#8yq);McIATE$F
-zg+4`t2wZ42vLlp*{trr>!^Cs(2%axPr3gI8Q|S=#c(9ozqQTTuMJ$LmOA}yB7wLGq
-z|Ia^ao{m`V=^h;9;l4Ulk$eF@4-bJw@U;unpku|;6*so-Q9LP`{-+<$i&3L!w8SZ(>x#wSA&6arG^t4W1_t3ge6%}es#inoKS}X<7g)*
-z7YWXqjmNkh%Qv$z<-x}EHG9Acv)P05&Fz1M_w57h!Pij5Ck4txvkS)9R6Qb}t*6bm
-zB-xmWNv>wY5I;@~5~CN%3FoIHa*o`79Df=znN!LsHPg{&>Fe6CY;=dPhUgMmy1ETK
-z2NsX#kk6~&RXE^y4!SHnWVFNt9(+_B7j4IJiEIr6Crs<>NSu^;4Fo;rXDpTAgo*|B
-zlEMjlbpcLoZNSE|+6-+8TN53_#ZcyM%7UV
-zvrbp<;bH!MIy7wtXGvk;Ob3Ao_0hEZUm?2*>mdWENorvv#f%~s*bXw8j`yIF2zq)f
-z5ZZeh!FmvlN#oLRw1Hd@?N|PdMl+()iKsTjUTzTSnBAqbN$NxnkqbN+oFx|U4`gs~
-z18;f&@KMQsILvWFJ20b!t2R_A9iBku{uV@MOm__3GF0)PHVKHUkdMB?fO!D3&Kkh@
-z0;x68Hu)<*hKdw?YxUmZ;ubhov7N}$2=Q`=(xN#}(mBwIOjrwhDUPk}dpj!5+RV&=
-zvP)e9QpWSh4WKPFq4iP&h|Z*Q>5g=~8buv~it&qq2>Vji2^2E+5{s%S!F)4yWz5D>
-zBVh*zF=p>U6cHpg1r3umu(TrPUr;d5f}(Ukh$x^?o}Yrr6{w%a&QHnEQW<4b+wzoW
-z2U^3uPbL5og(j(BvwzJ`=^{6%+I*9cl|C&ksjC8wj5Kk}i8ybspU?5K?PG#*IP`pM
-z2qGB~E&-pMb`Zx@dE@T~yz%$a^(aKZY2r+A65JTv1Y9AAvGF9DKB9x?A?A2XstY2f
-z1|oqJf2tZCrXhGdLco*NXl%SS&I)fsv81{qBD{neh!3C!(V~$wB$YfBUyjVh-@tbf
-zx~R_y&&l85Z!}{X8BI!1X^5H@*cX`sp27lNuMK1cTN5ma!=PZ;m*7VvlW{a00iqCb
-zFablmltR&vY=mlnUTx~XI)Hr4FOFy6(D)cr=|nyc^N?j&X3yOnQ$w%}c*t#JEdPMs
-zc(QB=Rl@_`GK~I6MvC(Lic5+cw-mF_z`(WdgzbT`z0}LcU;SZ6xb(vofBlCs{t?*q
-zFklh3ViqB;8PWnQ0n7mo-6IbGL>y5|8>g*h30vYUwJfy*-~jEZl&N$zO&G7H>4`4}
-z+=Qobz$wlvfbZRq@xq4VQtUJK;xYhU(QC903r0>BX8?E
-zQ7}Jzh0m~b-({PT5XDPHqouXNj_^I-bClwu;`OmB0WN)I=IraryzxWa6TnUSkR@!b
-zq7aL1j*xYAS$dGMHwzB~f0m3U2EIx$*AmiY!Ig=1om9g)Znq!2@v^FtdV8ml!0~l;&iWu!=WLc?5@Y~B?
-z<@;U@RrW&pcI5Zq15<_?Lfl|i6!)aXPRg^6avifqEO8I<8M-e%vd|CEDAQ}{))^JU
-zR_0hZvb3~Af`;8(xV$SoD8j-De$x9BDK%x^Jom>XA5=Ec|4l5%bf=+IH;M|oAT9(k
-zH517sBv6ZyVnP;`f?708{oqm4=88h2C{ff{8g4QLCQuO^1;K0LZSc-`UwkBf0-l7&
-z@;7*(VJSG61Ok4b><|{3C6sDlxWf1nLR4)1Vn8@w4G3qi^8W&mno35WNewX3lrz*B
-z;S7X;VH~{zwXQ`Pc;7%8Fu|zsN;S~6NCZrVap*dr^%Q1^NXhg8LjXj#;`tO3!BqiVONSXEeH;x_|f8E
-zIjs<$PEkh?Trv#9^;im-Atffp45nbH{)@?8h8f+-6u;oZkmX1ojfWO~5n
-z$g*=xOa|q+W!>qO?U%h?hvUzh^>K7?~1t6#fHvSARM3$Q}Z(nGWWHLMWU!N>alvG49A+w
-ziQ}~?PO+{=Zn*gKR
-zl9~XwmLOHWP9cjRDmKeO*VtI~!dTk@`xgX-C2_!_0Z_e)v4X>ZEp$NN8K4LSz%z`8
-z0B}+1f;c!ht2>7|1CwrnQo<4Kwb0(ETceBUVi|N@H5664YUql(40JhNj5xa3({+I=
-z#uSGx_PRzxqRSr;HGn@MUp5eJpn|B3s<4OybYnpW4~seg&ke*GKG4@?4W1_dXvOH=L%jS^DiamSxUayUCPAm9HabLC
-zVjOWI5wDN4gb^Gb&2l0nsyc8vT}d&dbP|q)ra_4W)WEUPW-%d(kV3!_*mxXX57Nck
-zK$duvF<(h34B%=pzy*)ibpID~Zvq}wkv)o6)!lmU+nwIK(@Q$tSvr`Ig%F6OSpo=z
-zAP^v!NRWLe1Q8Gzj=PS43nHTuLckCiiHbNnGT<^Uj0!4lGlMcCKgNZ0zf*N@
-zCqbS0&wKCt{@+WdZk>B?-L2}>+3M6O0FZ+o5)nsuffzV~IbP}W_EIJ(x|Oc3HEXe1
-z8_?!!r)igHg>QKbYI(gEY*7-GRx+md5gKl
-zESSv&`N4v91%z2R>(8l*SD~!*H%nPr_uxr+c?DBGdr2TY&H~KD`D?xd&K?t}u*Q&P
-z8&xr2!wAeBWZ*L`TW<
-z^A%yLo=FH&NhwGPrNsBd!N(pu_yjlk`mPwM+PL+4F0ZR@BT6O}%b$2F>4&U<3Qkm#
-zT!RqO@5iWre^1{Cj+LmAE8ht+mKK-B$Lg8Wjq?Fh#vNdgf*a~^BNJ;0rv}S
-zsx9OkRnkC`OhdtLeNq)-N-7qNF)g88?yAG81h-mudB8czHtD_zvB|Q`7j=4f&Ml
-z#&^rKJfms6L2ZB|Om5?6>V93O?V=Q}m;6Rqz_Lc5_|VHkszhrk;yXX?T6_Q`(#Zz|
-z{>KZ#n?KFsAA&lr2aMXvg~+h3M~l!EWt33$??qGU}VGvZns3cPD{Fb#p$lo_4KB!OIWCvPsoD5OBncP69ysF
-z((>a;=nF%Vp9ph=v&e~Z&@1tfPAk|wTDyI!w#F{{i|r(Y4ZVHnqQhsB{HxPRtw*LzV!SzqGil2g(;}JnYs}cph@zRbx0zW;%xpl{
-z%oGT-jmdZpRmMBT+S)ujw}+%bTi2&39Mi*~nSp^~ht+N)DXR%7DYhzBleFR;sXXgId|dqK?5t!A3OFxLiDP{#~u3;
-zAI_LNWA>~WIODm4*f)E|ocUc>-Fwx8rj-9W>gKcqA9c
-zNC_$=MrDt)q>Nz8p$2087+MP
-z-%KD2JsuA!2E_i>e%_JR;odn!a1?7eXR)L)+vlQdnBn+TiZq?;neCwBVE62e_f_Na
-zoPaqH4-luVx7DQEj-na|B;(HvC;rs1X;1y5?p}tK+7BrN%K9?`CG>gXmd3C5C7!|)
-z-uiapmPh`yY-x4d#{2HO@yPf&bIF%)B--mn6bNrtluk>$^4`4x00;a{_{
-z>}kyR6i7XKGj1Jjo`R?Jm2=pH&WG!CvXc{o=u-&DpKIA6*)
-zRT<)08c$hI%2hL85mPvrHVZZ7RNXz#Q1V32Q{^`LAN@V0U-2%$7x{ozoKO7o*Ihte
-zrFLHI@=dg;-)+m#u14F
-z$(;jwkN(%U@C7iTByMwZ)Pd-Cb|J%jht~485hH(Acj#aX68WdtS>?E
-z4_B1K*NuRPa58M{2ec$~fYX^nGFqpM38i}U*&gvjShfi2nFlTvMXTdQo{WeSxJsy8=;tg8
-zj}eADFAGm}P7Pg=G0WNDUGBWbdu?!CMw-oL>TnYh>A)H~6FVNz%caq%CRE|8_7NYm
-z#rqr)hmXrAxRYSQMm>$CBtKT)=%n7V?6au=L5)!Gy&zWh>lxfud!6b4q}ZD1e~9NV
-z%$w0Nao3uxjBs6P$)dvCu`d0fk7gYIdroF#?tm-5tRx@4dF9jf&wsLV(3Jtd-)plJ
-zSl$T?{OI?S@2(i!JZScZblUJ#(wE#$z5q^=9@pX_WE~wpBdYxj-jQ+*>fBDfcggKt
-zZ;)crm&Q-}6d|r%co!Maweg6sUlg^Ro@>XNe1vzyX<$5BRc#I8AlJ-M;r*KwBTp!z
-z1!qt>{J9ikKNn{{6!ZiY<9j(A#_EgC#L5cu;e8sRuF5Nl=vzTmQHg^RD@%xL@5;rd
-z#6KUnQ~MCs+|PY+)8th@z69tzH2E2?h5lee1JIFpobRja@96944@8?>swvS|k^X_rOhW(L|9#t);2q9WLI#Z{E~?*WOjoFWOKVzq=H3UEJuw
-zgZ9?VdGgY#nhBRr`p2H^saZPr{L;gx%;WB?nQ&9(xbfVg`wtZtWVSqc6hEy-&p4
-z_mQR-Du;}!96Ua8{*MO|`xY%N=OrSQIIV7d*{JM7uGeS|B
-zHf4U|zN;Km@%-`y$HthPlZppkb!qvsoJdwIC$_fg^2hsfeY>_~4o_e9$YrCd@ri})
-zLx+vckG^e*rP-aag1i;us%M9y0k@tccPEzfe6+OxPK5D`f`gkQ-U_`i7`KkVlhACm
-z0`VNzfayA6B%gW_))#e)Zrq&2xA*K7Z%u5*E1;Sfcc7LDv?880fGg3A;wm*$xyhRO
-z+#HRD`uC@${QFC&e}9R|~^BHd-
-z6#gqhnTHXI?m{T*QG{|$2*rjVl&6DKfKZ>i5xV3mgz{G-RIm-9zKanmJcv*c+`HI=
-zP|34O>IdihJq_suLj7$Bm5Gqx8_MAR17{*sek-KcX)L>6S>dmD_`S--GnUMxkq^5g
-z9QGOcAA>7i6xC1(PdIb>;^p)}Q}Bwy!tQm}x+5OaD++!YH*w;Un#J>2tkS~1zg#7t
-z1rRdmd~5!|G$d#Uw`+D
-zi$5)pH-1~b;f{}n+RKNI+#*{yWHi3NsAj)ew!S=k>Ro@hky~=;=H$sm&y~q*Z@u(S
-zrRV#9@b~9_+VM@*KmYodf1X<1wCkbQpI>n8cTW_JxaL`J(VnYzb^JZNaq_C10Y&aF
-z1K+&&asBzDPnG=U^S3gW4E_GZPtUKKGWpD#T?Mb*visrP5A9vG@}y53^kK(0yPt30
-zQ95Do)CXSw?U9KUw|q0Xu`#Os&cA8T9dGCFIr*;v9M?s=en$B%tIE!|#3J>5Ut9A9?X?jc)4q^XoJ(
-zv_@W<{AGOo?k|Sjvis5NJFj_lL+1AaXTmJkm}%SdE5^4UditK@
-zr8_=Nj30OEp;sne;j=H^+&Q^6wtmU$J71do)$iV)HtVY3RqqWU4p@o7!1b>OKG^&LY8`;OI=TMnrJze
-zr^=P|`$Z}$D*EMdDBB;7)9ZU5FYWcr-q+FNseO74h0BV2_G$XJVOIw)n>T%7)x`f_
-z
-z{fdKqiVBO_sx&lCYf1gD2XF25h5dge?rLe@g8seZt}6TgKJKcxi?U063aN2dpMRh6
-z&+fRZbGAcCl(rOjXaSA8+AyEpcyMyk|rRD!l6r@eS_-}%InD8nR
-z0j_{F4&X=N*l1FQGV!aRE58b5;5ejgIJX_He-zH&0C}A-DcJ?(IskqI%5m%-O=viz
-zA&@3Ps-cPOLW3c%f;0gVqR*uFgL>Zt*B^lWlaMyjcY{jwOW=D``96MYLQ
-z35i1i*ycm3hBOV*9gwy{5|LRwvINpPNQWSuWoP1?v-Pf`cspdbux#)Ag@tT)y0TkW
-z&vy4tu2u4?%awfCWlFIDO0mAh%Hd0fD0x<%l3OE%%{12;3p)opI0rfoiJ+yh#pEzD
-zgRmMv+c-OFfkZe_Jrd`vdo!bjTMt2VV9*3Hnu!9*P7b#kEro-1BuTzSRumxLkTc2|
-za%Qi|Qn+>SDDo-V4e1ahj(iHgPsk@|9XUl~)xx#{(pE@^ARUABEhLegg5OE_eM~+^
-zX7Ulrhg1P+8lEs(ZBYJmiDCv0U%LGn7JHz2)>3LwQHjf12i$6Mhs?c`W%
-zbVy*ZgZzoSikwiXN68;p{suY1@;{PSSpFL1e#l=ZM_T;>G+0mQEZkS&Ax9v+3bpnM
-z63B~tGp&K-U<)|})f0eiKBNjr)sUt^x&x9(4v~!3nE@-@9?QuMG*
-zWk92s;U+J`4a~5ufHVzKC!}v7B_WAWIs;z(4z@MJHXl+2q-l`WLHZVw$X@4LNCYiW
-zUvW2kv3&KVtLYwj8Gae?3z1MfUG~W_c_ep-51akC+Mo23Qsi&|`LkLymUe91|1abB
-z|1u)&U@f_w+=0@eA-1Ub9j!m42io!dtA=^%b^_p6zUKq)1>1St8u&9D`Q84&JHDF(
-zujRLE;K;#fJBF+y$ci5D^$$GrIxD&!PCVEexQ=G~18aRF0tp8Eq8zAFReN~sch&Hg|Eyh2O?bKzxkeC(lNKf7T~X*-@9&y((z
-zrbyLNKdDg4lR{EJN|(HnO=H!_8k5GL(P=aqQ3GnZ25D?mRgY1AY7-e-6an%f2@Vv)Q-`_DeX8(4{cy^NDm;4EgNq0(mZ>^ZMC(?`Jd~DWpI$y>@iVZzcrmrX0Zk#Ahpq9l-yfS?!bq
-z+#vkV%JK9#H)vRG?dWzqiIqb^{0}Gx81oNSPUA;GS`Gy@ex=-lO1TJJ59Kmx2IaI`
-z6k+8eS}iNbV_I%cQ)cC`J(-!Tj5COuSeYhguy>i)BTyz1VPzc6=yg`+bw@KT(=vz^
-z@%f;L-^Yqz5Av}hKI~ydCiN7_SBu=-UF2r=Bo6nK@hN4Dr&48%r=U#izx|svBo@Pa
-z2iDH0tDF_CYzSA*g4ED*%Zj4{(q7j3v|#$1|DpVd+uo;9pxMtE3o
-z&%nB0IbBCj4-606gX$_LPT5lzpEay?V0<7A-CDbM#JJ+pi=K6J_p^$}{mRY8(VG?1
-zXN@TRmD8p4^a%Q_Qu?e?`m7Q05$sur;rO^Ido*ZB?a(?Ur@qX9uMOT%?GT5&bPz+{
-zfg#s*-VP8*b|QVOw#5)0(qe=}PxTqxXD~eleTtqk!C|v{%5~kqkhcTxR8Prp*b*Ls
-zV#}{?x*EAE=M7W-Y1n8uq{MetqdYhLyFWO`0^&9;M`(0Q?u5}T6_-z)vPY7XV7PE>
-zK`FWWyAb9yN41N
-zkpL-}I2FFNZt9c{5SJ)>XsU$|Y{D_z9le^pwi2K^hUoXE((mIarR$X;|NIu;u8maIhUuy?hG5w+#!|23yWB
-zxQfAB7;FTXhUyIlD_wJk1zT`Ku(D<7EgFs
-zWs94JiDrR?E2qzF8FzVigyR-GbVm597DTZ)#)_b!>@oivv8-9dX=iv?i*xm9SI;hu
-zteuKSt)5NAqjp{#k2-ZWM7Jy)H4UO08Q!pYI6V5+ku(x75V#hyp>|3O-jIsoOTS9_
-zK7|ZK8s-OEwBaG)xtr%VK+||Ox1jN>LaiQ8{6O*)@>B*lPn;4CwN!Y+wbO_B_Sn$o
-z@vHW_<3abuXZqyrku6Ha?lGBFz+mhR%<4YJAXa>q{{J}QbjwU=k$ElJp$(hm0o@VO
-z|F>dKQ#|QLH8`JwTVk=6+}z$_qzAG6l0JDW+DSOPR0i3GR-Y1_x^_Um>;JWI(7zJW
-zdE&nh@C@fv=-0wXkDjPduT&A(nPO0aJc|seXSPe4%hj;YI5@mTZWyU-YjvT}e|b@R
-z@+`fIWj&Xvuh}vncJcnei}o*iJ;P=WUYL&(pt_qk>n=L?pQC{T?y0fAS^V!_vB3fV
-zltb$7MFoEuF#O-dG(SLIA}{^NsOJCg2fO~afx3kbxUqbqd=81X9
-zujqN2lfNPwjaf+u?L^PudFUB*2)%^Q!d1J`0n~=5f;|j9jMkvL(I!xuroxe%(Rla?
-zaQtrUPPU9APh92bVd7PY+{1M(Dgsy>i;87JiiaSXR?mi2BiCuUNHx%yjSh6K~7)tq2
-zJ(`OiL>;&Uk02qTE?Jd4iX8Bam2lSwP%GLGzjpL{bOIZMv&qMkXOSD_q08WV+R&eH
-zCzt42pQwNu6QGuIP#K(Af?hzcqT@J>UnENegHR~Mh1JP-kPY=kHSo4i!j)g(f066p
-zw~jl)4^Iw3Ca9M?X>Frd&?ne~^KmtvL~_Uy@(8yKY2b-{;Wrb_gZjQ7?*1_dDf@|m
-z9OEA2f5-nMrYBA%P0%7y^bq@WMOh6x*YIfJ-q!r=n?p`1Lzp~0DdRYr&z#xY=U19hj0zP7Jk>^
-z+wh}!Cw>aI!BdXoPx05F@ca+{i2z~~k$6G1rV4hLEF;&DyUD|J85MGz{GI&3Ik^lj
-z#+7j8TrIZ*-f9!K1%CUuPk0Z1j88&67Yg?ZTZNs%?}V3xv!X$|9yG`|E8GMwF@gw+Y{MGy;{FnTfLY?qO;d43R*t>-Gaedu;l#JkC#$e#e8
-zr=gi#6#+~oJMm3qEp8*3!b))<8HmTCvp|;bCP&Cta-Iz2s_Hp4#Zk
-z`Ipfd{vdqYpWx0bMFYN$d@CAID`xgz{0djV$GAVD6WmEG@!Qc~cpY})GvrBb9ALrk
-z`9Z=I6yhF6&vK1;E!sya5z_snxfSquEZzllVInTX-*ZXCk+Fb9rQB!e2DE^@kIq0p
-zz6srjXYzB+|%&($H=o>6@OM3kLN-USc^8I#^idmN|?gGh36m+PeKv?6i~M{Tp=HV
-zd>v4gI-qI$p?7xx9T?12!4Vf=&KSUp8lVsl!tZ{d6FgwhJm}+-fr|YJwTTl+JDMYy
-zFi;(Y_&+Abqp8Uq=z-)Mv^aSe>I3v(Q*sU5aVPp5-GO%E4T)>fQsjr8^D!PH3@68g
-z;mJN^Gx>l_AopI}zEIl{>_UHq-?P-$KzJT)=HEjTP(||A8Q_V=-`0Pi%M|
-zT?lfW7veNNe30*?=kJ9t`x-ci1*M~0oOS^5$#d}$dc9b~^TWfF!n4Co+8edvJkQm_
-zQf-s|2H^(1nB~xNuB=?YBVDUav-)#$b8?U`-4C2J;P+dQ#uXI}vP^13I+LTwYzBx1
-zRjCo_l!Y2mL+{S~>uN;m&|D)Ww`
-zB9Q{nvUEl?b!c+K9=oNok=;z(?IfFP72TZ)@*2FVHN9}=GSMDgvI@mmCPeWgByu~rQ>ary{
-zKW&o?{TB=$zp5`C%o>&aiudt@01rw@pLm|usLgd7J-Io?+}tu_KYOWnK<;I^^~U<#
-z`Nny<4F#Kx8*?6XJmh)GXwRmzS83&C(RqY)5@gK|_pa>y?&q@)yN_kRW&bE!Gt7bg
-zY+eejFe?oZRwJ+mVd2GUC=_r8T(P{|;xfK0?=pU5-Xu+JY_?`zY=vQy;Wfh##vfvq
-z(qa?l<^0TIXJN?ZnwGO9hvfM3O%igl?PS+}*F83$PhwLAJghk@vvh?%PM!62gy{Zh>UKo|HnoicAF|03HJR)|nd%f10LT?_Q&t>BNd!>faWHK>@q|AzWuwM_UjwZoI4Yf>l9-_p~uiZflOduCWS
-zo@t~J{jFtXSc$fTl+aR*kfFv%$`iU@Df^WaXEDF9tU;uxka~>SsUKcA%88PRsP5DK
-zfF;j7Fn{;K5ltgY7Mz%aiz;tgw<^8GwfOkWH|-iHYn>Sfea@>6FR3eBG;i*s(exW?
-zhW~EE*!5#=CZi`aqFdbO(%ME><1M4((??yh^6XC=F71y$%J#|GRrwXgOy-&PH6S(ZM4Z?{|7di
-zwnk55cuo@QY?2p
-zu5UzuJVkVls)`A0@+x!9v}}6x>W><>kCSz6xeG=%J;_J!+g-V|s&H*r6WO?U(crt@
-z=sKv*bLPGWexWDR<6h<}T_cH;#Hm`&Z2V3*FLK%x>1UI{YIG?;n*umWmZyzYl_HulvA?#$SZ%E3YsD#2t#&QHN_aqfM0|^XS3E6#E&WUUL1VY-bOOimL=+{h
-zRs%a)jV2R1X`J)p?jgAR3Q$G|4Wm-*odO0-M$F1pQkiF9yWZ9lj8GD7=dem5t0d*8B
-zteEU<4VBTLN39N;ecURSiAu)y3@qEDS1yj#V#RzLw|>N9jm-g1v)Rho2Ch2a>Y^9_
-zeUDcuj_Yd`S_V*KtVoHNfoZhpo7(WMuM_j}p^p>W*9jdL4&s)?imsU?usU%C<(I>N
-zUuFSyF(Nl!u;1>Y6`DplAqC77<(equW+$wY&TSYWj?_#NYc+Gkc^XZzJit1@QR1qU
-zM_WfbDqVF#op!ujZ>@KXcP$bYX=lodtcx5oUDsf{Ruqg^a1(`zx+@F|xmm(2-9m%T
-z>Ek7fPp`LSdgHXEy^K6bEL1(^36hH*m1OnkS;m?qH8P0;Q2HP#ZrTj!jHhKriVGx6
-zCte06PU?Hoi@o&tWt1BMY|2Ci6BUM-bjAFRYmkp2mx&=4V>pN|B2$AL4AJ6n%K)l?
-z3hV1BE~Tw6CL#By!~9H5P;oghrmUL{e!(07)74_|32U1v{?&+bt&(XAZP(+Ss~x!=_dc^RsSWk@%$R=-;l#er$Z>k8iyB
-z${$~+!-62QKF3Fql~3P8lr#CxJqipg}nS+*@WRI^+2i
-zvc%cG&cb125G4_dW*yQQ4NMRLo7boy0X8weH4zI27U#h(jg7W1M(n>OwAnYDsi>6TysF|Fz>hikNA@|TW`rm*~TnWwmHk1Z|jdrt=DKaYVPOmGdziR
-zlAZb;hJ9$i*kSquP{j|lU-4g=zqFncf7JTuVih2Av#l&Dq8aNwY-?4CiC1Sb^HyZh
-zNI*8t5tB87kV)c<*brgysY@y58G#DsQUDvVElt$vEm2*}GLavztFtV$tg&pi=qx&(
-zLzp&)(l9+$U5_nnrKfZ7Pxs~1^ruKX@b8V=I6)wyBneubPNO#%bh5<)U3~Oj0a=6X
-z$;;xib!JoWWs9T`*~l5&@sn{~uv&+g=+^0ouAS7xwbd59
-z#InvpEObvV3%Eg8Dl`k60EPGA^J(XSypNU7xvS3A*Sosv8{yyM?gIFWZn`e3KlpT3
-z6Yy_g+f7x>i+vMN`d?m77dJ{Y`Q?O%$eSbW32uls03~5F`harx1o@N*-@R1?Hd?
-za1{4f|7u6K6iMu+Yo
-z0UxTb4dJ3RXTQ=k*un;fxhxKkPCVc7R0Uu3)Pb!fm+s%4XnX#tocH)>*F&c*uam`H
-z_y6%Inf=oVvS#0fW631w*k5sbgqH}jrD6&7M^A&o9Em&dvEq}(B)@oCF{v)b#db6@
-zhH>!$l7Vul3{_vR%y1hA*Pmh6o7C%HhwBUWVvPN%!b)%1hx~rMelQRWBl2(alXZUV
-z*TY>Y3oZd7{0PvzsUS_6LHqtZo*xY0p&CV#w#a@n(m10*VBL(PXqf`6)lqJuW!yx|
-z6e&v9wVpt_{9mid|4OOJ->1}M|IgLrUNcx#^_a<*{W!0r5jBE_*YKj-<#7>FuLGSO
-zIv3^iHS
-z4}>3W=RJI1>i~CO-(YK%XRz;bYn^+%Z@P7nXS#2txYB-}oOj8fyqJwn=Qu}$W2u94
-z_{>}6Z8DK%-s{szsDtbRyzgZC09*TwhT*G`0WI!H^YMCT+(?5y(dIC!(`ajq>O5o$
-zbjGz=xy3CYMtW$7@V${}F$)zwn6mo-4mgVBOevn3TO0tflSX0)GHHV8nUasONyhdi
-zACn*)OtNtMDVragiUv?0tLi!p!elIVo~aX6l*M-e%RjBeQz-9hELVw+YW|>l|I&N!D5p_Y}yeHMVV^JUD=WM&4I5I-(uT`f5s+!;VWJ1h8eeZogkMR`cJxf%~N=i
-z^RYG@z#KN8L{3ALQWfj=A`rjYH?|0G!QC{*&Yi4Q0JOWv1o75t8O2;KpqV-Ih
-zLsCNkQ{bOW#g|eHUs9tnP=G~%V^SEK&ixLd~@?aZ|$tO*M`S1(GRBbAbi7s6k`-5#xSVb<1K(C~g+|lu>&4qmus(Z9>P7
-z!`aFo@a^U7+a+`~ex;UuezkUscAK_E+o?UNJ*$7a7W?^
-z2kP8)G|rgF>qIFcAbu;qjc?&Q`BS{u$)Dvx(F^j&VUOoiU&?nY^gR1go_#6LzLZx3
-zYS-|okLA0+ksqtM_#5G)ln*SgIK#S@MF}=jzpOEqR#If=;9GBMYir~GcI?n6zYPv`t*_u=n6L3B@IJqW_XgA9-yikN_g&f
-zjxa%9K3rc&6Ah>_LTkVQ-_W$7&IxG~oDFFW&WFfD+=Ir)<;Oh+jnS=}Pv&v+g{uuz
-z)B%xEyI;4@U~m{V8a^YODPx*>iFuuwGvju$E551#(U)p~*P;PAP66s@k=d+AJ@4p)
-zSI;ytHP@8krF~u>3n+_X#+jTLr`3r^(i+4bS`1(2vu7TYaDeI93SrZ+-pN#Esjs*A
-zuv*RajY`DOSp70}`1Am!!~JW|ECYRc8T)oCHSFfg^{3&V8q692*=kc{t=P(@OG>@t
-zhnDY2|MuAviGMBo`sQao4D5EVn|jl($8Vf}JKo@Y?ifzTx~DN&zk9oP!NQl{e)py8
-zX`5vu#JvL=j0x|K8?Ei+H4U-iLaUQnH~tjY0)PkknXTic_$UB!NVYa#F2iNI%kXeA
-zTyvSWTCT$r$wbXm?KpWMoiQ*hhOk1XTLh~M$
-zWazce$@XFbqLY@!y$f5*Fd3lL5sgk4!30EGf6my|RU*aiS&QqL45>+e+-jzkpk`k^=PlD}9NtT*;??S;hd}0W
-zlo1;wkE1NrMQe*nuoz~uLZyC1A$vA?ylG0Jnw#16;*wSK@!#&^G~!*?bX|F^_904-
-zwu9964A3JNu%HX^kX3KO)_%UJf!Ufx0ba{YTN<{N*fx{urL<2PQzkG&3ecwj5S>5W
-zYxNXcso5(dtJp&KL1UEF+^pts{{6k_QRO_8m(`q}k6#8r#5Bq`DmX!3=Ue1krd?@T
-zW!|8>$$X#jDRaB|E7O-|*<>&TEoK{NOct|2YxR2dp=Yz0u-
-zo=ztUWl(t(xm=(DY5Y;s!=e%%lNtFG=~86jhnc;b>55`~Fmq{Ub0(LW;rjm+OW8c=
-z8XEUj{B`LxJaA_(2?pfTD&VyBti!ucyL#+lYz&7+7b!2xXS$QKj7H@b*2;%dt1oo_
-zQN*cd7BEfRTxOOBSgZq>wW?87Mm~;v++~&wcbOHEDefziGi;CoknCmMXwx$^1Tt+}
-zn7f2z(WowPn8~!^&~~!<@EfaNf4eHXW=!(jOErrp_X&;u1aIGP@7VhuOB4tl)qhy^
-z@Vn`e%&}J|8gbtnZ|$#_x~}GmN>`1TyOGkAM}de^4-Y-MCvF!7Kh;MO$I~X!X+f9J
-zFiTOoY%P{1a-)K}ppoc2Mqc}WF=%EOctvlCkyiIu`FwrVxzn+qkCycuDkAo~hV~{9f
-z0V5$6`VLz5WGxNrMUS43)7UpPT4v#=(f?sP)_svEP~R9k^Gj2HdDm$c=DM5>=O~7J
-z4=jm3+oA_|%mqLImCp%NxmW+@AWD!~ByQJAY(^XDrctCsa`
-zdduR=FTZ_Y+rw=m7gd)uk-NI~-qv@-iIjn;fl*!ZOa5=d+o*up<5@Gf8GIAB
-zoaZB1C0v)*H>y*L@
-z5J>^i6p%&hZ#adrjnPb!$z?_QnTx~2B9-}5gOkEFk%jvC#s#L?wpp%K`qjqO=C$(G
-znN5+6+-CjF#?9v2d{7lhai;WvGRp|qi7b8Z)2ZT
-z-kZI|8*v!>__HE7A~@I*qLhuUMUER?mjp^3NzgZ@f<=(`3{O
-zA<)>p8cF0iBH~D91{{Nq>h0s9vEpj(@LHx5U%>
-z&_~io!yAv%Mo|vVp&}+lvm7;|B0!!(_$C~+(!o!9%9`rWEPR}q@sWwN+q?VDP`Yq^
-z)oEtfJ;TPm>G*+S(xvfjPty%EumPcDgNXpvGTEpHIYCNFEA^A2LRCl4RAR0x<3}lZ
-z$?4#g(87R>nbErEjMM(GcFC>@`K33LWNWo@&`%)*pW`rFr$n
-zpMLvl;=2d%d-CGjCJ$*ERyil^oE|HEY}S$&XU=sQ_E%JAU?~LD>pj-1s-o)v3S^*?~DmY1!0bq@2kU(SVu4)vN
-z3^q7LIJ%%GHN=n_F31~faFy0C2pYf0iS1-*T#Gb@uj4?lNe9-0#H$B12%=dMH+~#{
-zj6cb9TX@X3Q$cu~p;J0(;I6k>;fnH0b-G0uAf|3J69NnTmvoVgFc;#|LR>UUlsHoKCYkcl@46n^{sI2S1H&_XMN~_}2NT1{
-zRDAD&Yi_%RQv43sY(hstDvZQkq#V@D@@XI@u0y*KzYWfB1|L}PB;X>T2X!1h
-zdK4)(F}@30*$DDgYdmk3Wr2;1mPgyJkgu@udV`~%boaK7sa_`g{
-zZIPV2sutc=`%U7t#7+3xgOAjY>3d`1W}(Amowa|_^NFsmr#XDl3
-z@4p-$8DIwIPUCT7(kLRV;q@G^Cz41cL8rwUK_`K#@fz0Ybr|y+jgCi>rc%%mtc&v}
-zLB$$t?5B=}D@;t^t;MTD9NQHRMD#TI~{6gLs159*KWIsI4zV`^PJ0@WAw
-z#yItY13XZXz?S9$;VwCO}}mG%7HIt
-zR$0(^3pi)|T0X;;F+e*?J1lcj#;lAr+S|1^X6{J)UEWKaQS0=$oCTxv-gOFI==MY|
-z#5z}WuS2X_~K_ub*$2Z)}UUWtl12%FOAPIaODypBbH*y*#`egwx;X
-z9yZ*SeP7-^1&`~VGCY>`c=q1tD^W+b5+YEn$w&d=6p)z$veht9N*M}-Q$S`4NT;i&
-zSp8*FHCYjZj`swkc3yu;x`!&v8SXq<;Q@DryV^a?z1w}vEt=f{_Y(I>Hy?1{;U@0i
-z14`Q2oNUl!Y2--CH{zJc_&6XwmN8+A8`>PjilxzHDaQDcy7YzVB;9AH!HQYGTJNLE@ewQhMPuxs&NPDm2Mixx5mwex!IC}
-zHSTtDMXQvVOBdJml^xH;x%4UYirkcLkgKk8OM$=crx)dV*rP&Oxy22IorR>Lu(^;F
-zQtN0Yaw((ZtiJ`-7>zYSHRvJ(;%@p9!Avu=Yp?*BHDrpQ&8MPMGr+d+Y;zsc*ruS;H<3yCQ{`
-ztsgsLRdygY^4dAB@pa=%!@l%1U1rhXHFZ@Tk%Vy;D=_L!&
-z*+D~a6=4*ym?N@$+=69G+|poaw(u+%XpJ&xVzE+kXHi2i10+(SXJP1OVX=oP$+OT9
-zt8||&_P~aM<3rd$!E$TnA>hF$rKH5CtnU6^R(HQ*aAUR-#TSP8tWm4`xZXNcW7V0i
-zSg+=!#IdWK$^GRtF+&Iyz1T>*D%F>$W-00g(Q90t>p$!8x<6k%|D78e?#Y
-z6;JHEcIEbskKFpxV_Pw|`SQWU^y6@1edF~PkDPepFm1CTiOada0&Vr9T)ZUSpw|Ps
-z>La!>`bwLqO?Ri~>7%y1aGAcJZIphvZIU!aKUe>w?tkp2OTu|sgTjNd#$;{D+m$k<*aZoOdGYtI#*$Z?h~;hiGpnd)l&nX%e#z%0X1X
-zY=h0H6CDTPt(~lm7YaU~Sy!3iGw2-lqDYZ0;&L5#V%ZsYHaMG|d>(LRQj^CT(a9Ro
-z+1-dv)`(6AJI6vHGsVxUDt<~MI;lZ+6w?5gn{fnX1TqhqkC{)JlV(0(t}s`dITl~H
-z#_VCFB!iI>AH9=GKv+wf-LbsoA=;STJ~^~8&rze+MeW&j`aCuLpQfWJr)kc_fkqIf
-zowQdnC%Y`VAg0o)&JxhqSTF7EGBJC%zHsRBwKus;cty)!&MtoQwu7s8%=*i=7ykOd
-zj-|#>g6E-bz6!
-z%1AjBkLJgY(tr9x)-q)s2pZ<^hV7Lmrj~_9@D89MqC1oY3E%Xrp>T0Q6jN_b5~VTs=$Yu2KCX7$zt#OV?!f&)
-zyzPwZptvP3;@!&%4#u7W(V6D_>tQd*=095?{Uh
-zViMordSFw_^=r1<9w95&&ba=@8-x2^ozpsV+QXOle}8*t;(xw?m#zVA-3={Ed-Ai2
-zs{5Qi;G%@krHHp;jVmRmxTqAHP4U=eVMuLZOx)tKX*SHJ<3PlIVykj3N*HYV7*@AuS@`-Ks=YBoP~`@
-z{F|p<{H&inD!=!G;>80zV;u4D6^_Z_+1x_MBF~)gYR_8#t)5%_4?3Rm9Q6Fv@kQ`_
-zFzr&uBaUYr+<=^!BFUo82w}i7S12e3v;EbkX(nPa`RF6@+v8~9Ml!dJzC@q{mmxjw
-zAGf#|F2U!xWEXL5$pam|e;e8tX|m8NGEoEqWBt>tu3i0)N1r}H
-z{`kc5m90;$S-TxikXJ7rv+n(+2G^toSo8i#EI*j|Eb-68mx;a49^#50+JE@rTX*k9
-zD$ZB{XXr|RH7Y&OdHlR!1ODaIrbIR^^A~5H1s247wvj_ojhJ*sTm5LhvgAEmn_D%s
-zTy&>%3<0(3a!C~UMlmGAEfw*OVIX4qTK6q`?%5^=-Lj8b>q$`m0yLcik`0bFY*9r2j_^$phW>5F)HfdQqQ!AgWCE6_kOB3pDV1+{XyBk<(-jqKaCG3cKwqWMO
-zk|r}&?XcQeV`fFuWZI4vnbHQaX2ILrRz*B41JoVIdYevSAVar-d$u;5hd#NYyMrZiJC-#Rn6TywQ=gJz@eUClcVi^QDfvQoZsVa5z76bharhQz<#+I;3ZJQ}aTPrbPFr3)X`@2YKoOiLp*mt0~E`9&eyZ#DY0hLj`re%8xlX2tK6=}=!Wb&&$>
-zq}jzO>#Q(`ZKV{~jS^g@olR`8vpd=uO4xgP^TnisDo|(Gf}bkC@+*cp^+iPGMNTuy
-zK>>D5eLg+LQBf{~9;A<@SK1jfv$HSl`M%V{;8{3deSuV>SXo-)z&Vb~9G6AEFnnDg
-zXbbRKv=*=7munjJ%M4c=S37S(oAIsuM$LNtjfRcJ+njG$j-**Ls8^dWNY}t_4F>aR
-z)+ZRHDCy4$8j#YW{&m5kO}8b^fAB%#{GC^ATrhXT&9mp+G~lu=6V~s1=K6I{a^9T#
-z=Wjc4a@*|ta`Fz}bTEl9?!4ngJaO)gH%yzc>BbAmsx8$!ny=rrQ;nI&$C&kQZ`@8z
-zRYs0CUSz6z9-EK`5o~J3dR;p<#WM{?jcT_7qL1|x&64ILX1$ul<9Z{T7-m!!KvV(M
-zh3dyPoieI=KIKBCNPR(|X6>YPuZbD%QiiH8QbsD7E&e=8s8qe^Ugb8
-z!ebI`SYH)9Eoqwpg%*XZkWT1XT8C@lgE)&P5YFHGvU3DbSWU!@0Z3c@MTa9*g
-zD$aVqdriin5CC*q&i29`^HD*aL?mi_mS3>E5|)}XV(Xm=N|y<+Q>(l*Hr&_kQb0BX^B|QbLCj%
-zb)-C>&I6#v@$DLBjR#so?b+}NDYs+j5Y#~l(t)E#k8-s~k6w84=ux=hBs}0J;5<6C
-zJ09fXMoaMmejT}kJfPv9=CKxu0^zg*HW2)}vZ#fQ@?^wJ@sz8p)_8St1oE*4GpQE&
-zvx==RB`LBN@)(4;(X9BiQDRvj;Ghr}h~UnaK&KL??
-zfKk&$3yR23ZG+#Qc;Bb_%lT^utqD9k;`M2?-Znz74gdwC(sFbBAuJfonL>$BDF_vT
-zmH-I^GJHk8AwFug5(lJF6V@2V7*D;X-Z;fv@3_)4U$f9S*Sy%V*wY#Kz;MEO!u@I5
-z-<^MVf5z-y?x2uw&bJi^73R1w#yn1#Eu2XIAO1(#Alps6NRZbD-BV}xne?vA<9aOX
-zzM&duMj>4C=*KbDFIxsE`6a)f6Hfv{N>nEq&n>>^UsR72qB#q
-zZ^7Ge3qFha0RCU?y?J~TMfxya)yEt?_mxa0$MobTfy~u&K_Cq|AY2JI0Rx%L!KEZX
-zKtWva0tI$;b-i!b)vqTpD1vz4vAVjt%6hH3qPy$Y6O>(D*RL?0-&5T)NjUa+_x-)^
-z=lx?RsqX6T>guYep0lo}0k?rdsCbN&=dhko2n)*+9?OJMif*^Qh5Lz
-zJLNfC^1@zRP%?Ss?9(BdGH2k<$HRRnGUh3+o`-tEAR$^qT*z90I!Y)9al&0i+w(`r
-zuQB&Lvi9Nb-D^bYFHb(PiW27ibi)I`Sij)`_VLVLuV}mC<#p26(%ZM-YoDIC^VMI!
-z{=%!!y4$4{j0d==6*R$a@h5uo81s1ZG&9q{_w$sSAEhrV4mbkElZt!!D|yY>{IP{o
-z^QRVeXcp_w%0H`crDm0Wh54NPRfPxnH|!rcKXAQK^0EEnl8^X7KCg_aHP<>q%vf^^
-zGu6Dv{NK7SizTzpVx;qmNTaIqpiz;rp!{_Xn>mr|;TACuH6!#GKjd
-zA99Q6zLFc5%QY}Zm~k0_G}ni1K`Ka=VVt=wb4_R-(S+ucn((_*n^0D=fMLl^=!Pc5
-zC$(X&2~TZ6xdkn;k?m*8u{cmiWHYzQLXS{Kw%;}Os^yoxzHSe-`8%QIy~1AOdX8&hQd?wlrWT*t^UMKT4}+_^>uF_~AB^GS%w
-z$Sot!k5hE#{fHj?=rO%T(C~V)l&`kK?<3Re8_!z!h(>IY*X8YPiD|@u5)H)Er3Eo+
-z-{9fB1rQA=QF6CTj*7annA&KEU?bPx?zKU*Scw)x)Ip-(KRh($2`eXgXrcq;7w{BI
-z8MfT~B27L1;t7d;{P=~;mLFP}t;e^L5&Ju(S$M2GbJvRA5}O&ef*r>M*=;PFuVGm=
-z!%z%sL)f6BXuF=VusSuFrwa;7k;QbS9os=K(d!Kr9CxJ-yLAn^HXSXGT}I@wOBthe
-zGDhoUX|F4hxuC3=5RWb-F7v(uoBhF4rMo;ik&NZaG8{F`kg=Bt?x9(_mSAwZS);6d
-zFlx-EN{yK-#9E_TnTA6IIT4Up>HQ;9aMXa~+a8gYmxkSu@FT&A*R?XAA3F3u=igv#
-zy^1;O`0dZnND>V8NVDj#KsLMZ-?G+tF=sc?I=ZO9WL4=@HqmP0b)ud($-0xNz+3D3
-zz~y`uls0lAYo(6L-I0ZROhwp48uFZ?Shan=X*UPrSOi0Zul5OMa!~8F);xpLTBWPf
-zR~f?kupwl;!J@0SR@&6}ApvPjH$_ViHSB93>9B4Uv|PwEC!aB$)q=1
-ztX2-u1$LXQ!phm&HAAD*<=giBF#Ax9p`&=PsQPv54e+x3HbN)IXacufEss^O7BeskJc
-z3&6{enG-IjJaNKl{`TVnqB9(IIn77q7;=)q7-A)mX%w1~%W4ssscJOW*K3}i)^D6M
-zt=~Co(ZT~rKlmBa4St44u4J+Ug+*dqX(VO@X7s2dX30(&>>&CHTGL6Ex!S6fF#-6)
-zL0g`@pb0CUUAEZAjjo@PZ>eN;(mBt5P+RJ*{nI1T*%Qlso97GCvU|V2-axH7{;S=SI_40(x)g}+1(0qQ^pZHvYr|#+moWm<$jo8VOdq4tS~OaX)?l+}
-z4T#oYoh+6Rr>(wHgEghR4cn+vi?Sdf!su7Eu
-zXIdvefAX|=GV{&c`R&?|6?BoOsmKU}%r9*>5^pW2b1hjhq==Awwc=mOVY&3q2`d^F
-zFPSiD(zqq|5~lKwHB-jkQ#HAvYi;IDVqr~{dYEsS31DGSvV|3+wxa@M6+s=1R-q|X
-zaN=Zx3~Cn#do_=}2kg~e(fGUpGw@Go7%go;`=|q=kHU;rZ&*ju6zNuKqn1)V6ivBI
-znswSQQ5#;0mr`^CoOxZ^q5_DYxwCVPj12M;BxSg)7Ssk{q7+kBT29IQRPL>?FkR0krQ>h@M*5-m*~t%H@b+Hz
-z@e>b!Af5R6Pq5*0y6wck(~ovPi|w)(CutVd#l8XB`*@M7GGS!4sx@ZwJ{;VGZZm4&
-zELzmJ8J8g1O!G8NKVZ3Km)zfy`R=IsyQ8w;m0cHAQWgP-GYA+5IA_NA!)tysW6={A
-zZmb$#2B;&=dIEosjbFW&IsSUbj%yD*DY+#c@XZDIWQI24kO5bUVe3Nua{Z0^d-X5r
-z+3ECj!_OGniYX1!t7tXL>1Z_q`DS>TX6!W00M1c*1EZ!7PzU5LlG{ZNF$^R@FLTU3
-zD*YJCa$>Pt;Bqz;r*yPuHAH0zGLDD_wOCpvsJD7T>MKpeK^io4w|E@kqp4*ij2b8HjGRHFv9v-m93}o?v
-zE*PUr$HZu+xVT>7c}SjTZP$ysn0{+}OfOc(^rb~`9+TZ)fOtj2L3vFSZNb#FnMLdJz
-zC-z^vW6^JqXWsit`irC?g8>MsH;|PH_DyVe`te|gVoAlEhbJ@B2#f~JK)UR^%v3>gU9VXVHrxCVl
-zkj6x5$o#ugmTVV_B{U;1&DLlDx%GOj!3dJy4#M4$m+y2s_6^qWVUd$3XT8-z&f+2m
-zDAr^ZHm|J0cCs2xiNk4kIGk3!R$Jn*Ley#jyO%dx>}F6Sw0e!x!I~^);BZhRmUfy=
-zCaqSZ0Sus=R;$H=G%nB_%@ehF7Dz8W932ou#Nt_dc^+e2P_Pg0d{|jl(CI3ek#RXQ
-z8COBZDR=0caV2(6CCpBz{O3!t$@=U1=X0@<2RyCc1R8_|G>8E!M}}?5l*?)T6>zgg
-zUcH3K>LsMi&@{rGJ$jK9vpUAwPES^2fRj~G9Lg}6oQd(R()llbSni5)IRA^^wv`o)
-z`Q$li^#SSERqA}Z^b&B*hU>2RvYbAgaY;wM+4%_l5UA>%yZF@P<3G>t&_mW5*l~P6
-z${Rem$B{3{BlL1FSp^fKo9V|5jC^Zseu0p$vFI&!n#IUe#H#H&PG6ywH__4JgIb&?
-zTef-f+-)1w{Qrzbmu-bK>MGw9`s99nq>zjkN_zn04mDSo$C9yORL3%q=i4+5f)g@^?1r(VVhLPu_@4dMHJ0ah$rEFKesSj_A`+ruHLw
-zE{`QPydRHIWmH!fRr*3~(3o9$CMt{m1p}W7pA`qHm*j(-ge{$JBL=tILLM@_g*P2EMSlPAj{Lk%zacF
-z`z+*>B8%#NwFlZ=cDV^>VquDv5ZQvaQXgWvhu(s=&~y*#!BiV=r$GAxDNE7YK{MJ%
-zbq!G4=zY|jM^ORu?maT;&B%P4K@FKsnd5cJJCE3GLHh8eM}E)Thn>=Aqyi^Qm)?
-z6FBcYQTr~1jl97o$o^Ohir`G$CK~l7#A$e~PfKa%S>;k0;Tipa2|s|MrrRuc?8n;>
-zX+=_P!f=U9J6cQj^uV1U;^k5INX~`uN1Ll=PnfY-*fb__p0i_G)7-jq3ro*S&ChP!
-zR|AE579jaEgj9?h%AcB@mWC_%DRa#}KkUgplpG2-$ywkmFg!A7BAIFMJuH
-zqMsmCG8*vnZG=j}P%eD}p)yEQ4*67{1)ne@1dq{}_Ym?K5%R-30k{|ZMDg<%NkagY
-zF!ClsV;Klt2#u>pXgp{i6AqItY!RAxJ3@`$Bh(}z)C^^AhHtg}fY4-ke+uN=IvWB!
-zpB_eN#(PSbITgYy#P8A>d;|vn4IhESPOJWjlfdx5{rsUu$19$jZ+!ce2d;no-WM+U^SoPr
-zefX8HzWLi@zjprSz<0m9;oY?>25y`4eZUy`=8=aEG=ASb@O5MHw!6pQaLoxkPfKO|I3Eax=c)?WI_;yYW;irz`r
-zT~>1O(#vD7+wz^GpU-c4WA^UL9=d#0(}$P$Tz2<0x~2~=E4;npsYLJW-<2*xV>ahM
-z@ba^fdoH?kcjYC!KWX~D#@6wDjbrrYYc`JAe9cpNPyI0IlHE0yr*6FOqTSQh{oza7
-zv+w-knPcnkt@(cJ>RS%Jvu5`tnbeXCcPF;6PyM8YTKd}En$5)r_(Sj9{RCV*c=czi
-z-0;%uCEx$>v&)`3^3#Lw{9^yHzmD==u>Wd!{6YKo++Q6}t!}(zcX@Kl{()x~>??fv
-z>l;F~n?HS^;}5kDbR7CHbYSWylu;qKREwv=HE5Sn%_p9V`E2pN|Du3piAzN5<4jxq%!+W2jOR25s3Uvj#kzR*Z
-z!#A3kThRt4iUg8BwRP|~wGbgTJ$Q`16Rn2O#w@|}WGIYByNDr6`WWCWqIr;iIlx+W
-z?zePeJ|%la#2MnC363I!D(mBV-FbqLJhL0mhaH-z^?Wb{DAsBFaUQ6hV^
-z=CHUAR}Z*c@{Q^}1_QZKv8Sa)J`cEg`9Z}%VNo{X$je4dmTZL6%aPK7s;X?Hq(q7A
-z;W&~_=^nkFoIRkGk~2M!pD!n%2MWlCpT&-W5_dMj+2x3B0N&j{_zcb)m@_XMnc0?&
-zG&g4>jg8sJs8LE}PkANzLf$|@f&7KMfxNtI#A3-tv0Qo+mn4#
-zT#}7s3u@g1*+3HOfK$oWIxur)Hqtyf8>y(wMzUYB<{9nT?pt;U}>Xv&e$n
-z02g+TL2hYg4}6!L2eh1=lF0#z8K@thjZB@YMD{G`AZfh=TAd7B&4AV=N3;WCV>Tkc
-zL?U&*Y^16t8!0W%MqIhK><&3%A1E)+Mk=e6$R52dY?^4qK|tXkpl^_Pt;80Du^F3?
-z0CCd*)9xn4LZVye3=e+pc7O4u%kBFTe{OfVzdmAie+2=3Cw>R!&sNd-olYPA4!hh(
-zkLcXyV^