Merge branch 'main' into RCCE-247-group-permissions

This commit is contained in:
Andrii V 2025-06-18 14:36:56 +02:00
commit ed71e815ff
19 changed files with 232 additions and 174 deletions

View file

@ -315,9 +315,7 @@ Enabling ElasticSearch
^^^^^^^^^^^^^^^^^^^^^^
ElasticSearch is available in EE edition only. It provides much scalable and more advanced
search capabilities. While Whoosh is fine for upto 1-2GB of data, beyond that amount it
starts slowing down, and can cause other problems.
New ElasticSearch 6 also provides much more advanced query language.
search capabilities. New ElasticSearch also provides much more advanced query language.
It allows advanced filtering by file paths, extensions, use OR statements, ranges etc.
Please check query language examples in the search field for some advanced query language usage.
@ -325,25 +323,15 @@ Please check query language examples in the search field for some advanced query
1. Open the :file:`rhodecode.ini` file for the instance you wish to edit. The
default location is
:file:`home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
2. Find the search configuration section:
.. code-block:: ini
###################################
## SEARCH INDEXING CONFIGURATION ##
###################################
search.module = rhodecode.lib.index.whoosh
search.location = %(here)s/data/index
and change it to:
2. Find the search configuration section and change it to:
.. code-block:: ini
search.module = rc_elasticsearch
search.location = http://localhost:9200
## specify Elastic Search version, 6 for latest or 2 for legacy
search.es_version = 6
search.location = http://elasticsearch:9200
## specify Elastic Search version, 8 for latest
search.es_version = 8
where ``search.location`` points to the ElasticSearch server
by default running on port 9200.
@ -352,8 +340,7 @@ Index invocation also needs change. Please provide --es-version= and
--engine-location= parameters to define ElasticSearch server location and it's version.
For example::
rhodecode-index --instace-name=enterprise-1 --es-version=6 --engine-location=http://localhost:9200
rhodecode-index --instance-name=enterprise-1 --es-version=8 --engine-location=http://elasticsearch:9200
.. _Whoosh: https://pypi.python.org/pypi/Whoosh/
.. _ElasticSearch 6: https://www.elastic.co/

View file

@ -9,7 +9,8 @@ Release Date
New Features
^^^^^^^^^^^^
- git: added server-side support for partial and shallow cloning, so "--depth", "--filter" arguments are supported
- git: Added server-side support for partial and shallow cloning, so "--depth", "--filter" arguments are supported.
- PyCharm Debugger Integration: Added support for remote debugging with PyCharm.
General
^^^^^^^
@ -28,6 +29,8 @@ Performance
Fixes
^^^^^
- Resolved an issue with SVN repositories by implementing proper cache invalidation.
- Fixed a regression affecting push capabilities.
Upgrade notes

View file

@ -1,23 +1,23 @@
# deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
alembic==1.13.1
mako==1.3.6
mako==1.3.10
markupsafe==3.0.2
sqlalchemy==1.4.52
greenlet==3.1.1
typing_extensions==4.12.2
typing_extensions==4.13.2
async-timeout==4.0.3
babel==2.12.1
beaker==1.13.0
celery==5.4.0
billiard==4.2.1
click==8.1.7
click==8.1.8
click-didyoumean==0.3.1
click==8.1.7
click==8.1.8
click-plugins==1.1.1
click==8.1.7
click==8.1.8
click-repl==0.3.0
click==8.1.7
click==8.1.8
prompt_toolkit==3.0.48
wcwidth==0.2.13
kombu==5.4.2
@ -26,7 +26,7 @@ celery==5.4.0
tzdata==2024.2
vine==5.1.0
python-dateutil==2.9.0.post0
six==1.16.0
six==1.17.0
tzdata==2024.2
vine==5.1.0
channelstream==0.7.1
@ -64,10 +64,10 @@ channelstream==0.7.1
zope.interface==7.2.0
zope.deprecation==5.1.0
python-dateutil==2.9.0.post0
six==1.16.0
six==1.17.0
requests==2.32.3
certifi==2024.8.30
charset-normalizer==3.4.0
certifi==2025.1.31
charset-normalizer==3.4.1
idna==3.10
urllib3==1.26.20
ws4py==0.5.1
@ -86,7 +86,7 @@ dogpile.cache==1.3.4
stevedore==5.1.0
pbr==5.11.1
formencode==2.1.0
six==1.16.0
six==1.17.0
fsspec==2024.12.0
gunicorn==23.0.0
packaging==24.1
@ -107,11 +107,11 @@ ipython==8.26.0
pygments==2.18.0
stack-data==0.6.3
asttokens==2.4.1
six==1.16.0
six==1.17.0
executing==2.0.1
pure_eval==0.2.3
traitlets==5.14.3
typing_extensions==4.12.2
typing_extensions==4.13.2
markdown==3.4.3
msgpack==1.1.0
mysqlclient==2.1.1
@ -119,7 +119,7 @@ nbconvert==7.7.3
beautifulsoup4==4.12.3
soupsieve==2.5
bleach==6.1.0
six==1.16.0
six==1.17.0
webencodings==0.5.1
defusedxml==0.7.1
jinja2==3.1.6
@ -136,7 +136,7 @@ nbconvert==7.7.3
platformdirs==3.10.0
traitlets==5.14.3
python-dateutil==2.9.0.post0
six==1.16.0
six==1.17.0
pyzmq==26.2.0
tornado==6.4.2
traitlets==5.14.3
@ -175,8 +175,8 @@ premailer==3.10.0
cssutils==2.6.0
lxml==5.3.0
requests==2.32.3
certifi==2024.8.30
charset-normalizer==3.4.0
certifi==2025.1.31
charset-normalizer==3.4.1
idna==3.10
urllib3==1.26.20
psutil==5.9.8
@ -209,7 +209,7 @@ pyramid-mailer==0.15.1
transaction==5.0.0
zope.interface==7.2.0
pyramid-mako==1.1.0
mako==1.3.6
mako==1.3.10
markupsafe==3.0.2
pyramid==2.0.2
hupper==1.12
@ -230,7 +230,7 @@ python-memcached==1.62
python-pam==2.0.2
python3-saml==1.16.0
isodate==0.6.1
six==1.16.0
six==1.17.0
lxml==5.3.0
xmlsec==1.3.14
lxml==5.3.0
@ -240,7 +240,7 @@ redis==5.2.0
regex==2022.10.31
routes==2.5.1
repoze.lru==0.7
six==1.16.0
six==1.17.0
s3fs==2024.12.0
aiobotocore==2.17.0
aiohttp==3.11.11
@ -259,12 +259,12 @@ s3fs==2024.12.0
botocore==1.35.93
jmespath==1.0.1
python-dateutil==2.9.0.post0
six==1.16.0
six==1.17.0
urllib3==1.26.20
jmespath==1.0.1
multidict==6.1.0
python-dateutil==2.9.0.post0
six==1.16.0
six==1.17.0
urllib3==1.26.20
wrapt==1.17.0
aiohttp==3.11.11
@ -286,10 +286,10 @@ sshpubkeys==3.3.1
cffi==1.17.1
pycparser==2.22
ecdsa==0.19.0
six==1.16.0
six==1.17.0
sqlalchemy==1.4.52
greenlet==3.1.1
typing_extensions==4.12.2
typing_extensions==4.13.2
supervisor==4.2.5
tzlocal==4.3
pytz-deprecation-shim==0.1.0.post0
@ -300,7 +300,7 @@ urlobject==2.4.3
waitress==3.0.2
webhelpers2==2.1
markupsafe==3.0.2
six==1.16.0
six==1.17.0
whoosh==2.7.4
zope.cachedescriptors==5.1.0
qrcode==7.4.2

View file

@ -25,3 +25,6 @@ types-sqlalchemy
types-psutil
types-pycurl
types-ujson
#remote debugger
pydevd-pycharm~=251.25410.159

View file

@ -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-51fb13c9-6175-480c-b9dc-630c7601d47c.1.0.tar.gz#egg=rhodecode-tools
rhodecode-tools==4.1.0
rhodecode-tools @ https://code.rhodecode.com/_file_store/download/0-c84a5d20-64c3-4ef8-9c83-37e2b2dbddd9.2.0.tar.gz#egg=rhodecode-tools
rhodecode-tools==4.2.0

View file

@ -23,7 +23,7 @@ pytest-profiling==1.7.0
iniconfig==2.0.0
packaging==24.1
pluggy==1.5.0
six==1.16.0
six==1.17.0
pytest-rerunfailures==14.0
packaging==24.1
pytest==8.3.3

View file

@ -39,6 +39,7 @@ from rhodecode.lib import audit_logger, rc_cache, channelstream
from rhodecode.lib import repo_maintenance
from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi, HasRepoPermissionAnyApi
from rhodecode.lib.celerylib.utils import get_task_id
from rhodecode.lib.str_utils import safe_bytes
from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
from rhodecode.lib.ext_json import json
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
@ -615,7 +616,7 @@ def get_repo_file(
elif details == "full":
extended_info = content = True
file_path = safe_str(file_path)
bytes_path = safe_bytes(file_path)
try:
# check if repo is not empty by any chance, skip quicker if it is.
_scm = repo.scm_instance()
@ -625,7 +626,7 @@ def get_repo_file(
node = ScmModel().get_node(
repo,
commit_id,
file_path,
bytes_path,
extended_info=extended_info,
content=content,
max_file_bytes=max_file_bytes,

View file

@ -483,7 +483,7 @@ class HomeView(BaseAppView, DataGridAppView):
if not searcher:
return []
is_es_6 = searcher.is_es_6
is_es_8 = searcher.is_es_8
queries = []
repo_group_name, repo_name, repo_context = None, None, None
@ -495,7 +495,7 @@ class HomeView(BaseAppView, DataGridAppView):
repo_name = search_context.get("search_context[repo_name]")
repo_context = search_context.get("search_context[repo_view_type]")
if is_es_6 and repo_name:
if is_es_8 and repo_name:
# files
def query_modifier():
qry = query
@ -535,7 +535,7 @@ class HomeView(BaseAppView, DataGridAppView):
else:
queries.extend([commit_qry, file_qry])
elif is_es_6 and repo_group_name:
elif is_es_8 and repo_group_name:
# files
def query_modifier():
qry = query

View file

@ -15,9 +15,10 @@
# 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 unittest.mock import patch, MagicMock
from rhodecode.apps.repository.views.repo_settings_advanced import RepoSettingsAdvancedView
from rhodecode.lib.str_utils import safe_str
from rhodecode.model.db import Repository
from rhodecode.model.repo import RepoModel
@ -110,6 +111,35 @@ class TestAdminRepoSettingsAdvanced(object):
assert RepoModel().get_by_repo_name(repo_name) is None
assert not repo_on_filesystem(repo_name_str)
@patch.object(RepoSettingsAdvancedView, "_maybe_needs_password_change")
@patch.object(RepoSettingsAdvancedView, "_maybe_needs_2fa_configuration")
@patch.object(RepoSettingsAdvancedView, "_maybe_needs_2fa_check")
@patch("rhodecode.apps._base.ScmModel")
@patch("rhodecode.apps._base.IssueTrackerSettingsModel")
@patch("rhodecode.lib.helpers.route_path")
def test_advanced_repo_delete__call_remote_cache_invalidation(self, *ignore):
request = MagicMock()
db_repo = MagicMock()
scm_instance = MagicMock()
db_repo.scm_instance.return_value = scm_instance
request.db_repo = db_repo
obj = RepoSettingsAdvancedView(MagicMock(), request)
# bypass decorators
unwrapped__edit_advanced_delete = RepoSettingsAdvancedView.__dict__[
"edit_advanced_delete"
].__wrapped__.__wrapped__.__wrapped__
try:
unwrapped__edit_advanced_delete(obj)
except Exception as ignore:
# ignore error, because of mocks method will fail with exception which is not related to the current test
pass
db_repo.scm_instance.assert_called_once_with(cache=False)
scm_instance.vcsserver_invalidate_cache.assert_called_once_with(delete=True)
@pytest.mark.parametrize("suffix", ["", "ąęł", "123"], ids=no_newline_id_generator)
def test_advanced_repo_archive(self, autologin_user, backend, suffix, csrf_token):
repo = backend.create_repo(name_suffix=suffix)

View file

@ -22,7 +22,6 @@ import logging
from pyramid.httpexceptions import HTTPFound
from packaging.version import Version
from rhodecode import events
from rhodecode.apps._base import RepoAppView
from rhodecode.lib import helpers as h
from rhodecode.lib import audit_logger
@ -134,6 +133,10 @@ class RepoSettingsAdvancedView(RepoAppView):
repo_advanced_url = h.route_path("edit_repo_advanced", repo_name=self.db_repo_name, _anchor="advanced-delete")
try:
old_data = self.db_repo.get_api_data()
delete_cache = True
self._invalidate_remote_cache(delete=delete_cache)
RepoModel().delete(self.db_repo, forks=handle_forks)
_forks = self.db_repo.forks.count()
@ -148,7 +151,7 @@ class RepoSettingsAdvancedView(RepoAppView):
"repo.delete", action_data={"old_data": old_data}, user=self._rhodecode_user, repo=repo
)
ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
ScmModel().mark_for_invalidation(self.db_repo_name, delete=delete_cache)
h.flash(_("Deleted repository `%s`") % self.db_repo_name, category="success")
Session().commit()
except AttachedForksError:
@ -196,6 +199,11 @@ class RepoSettingsAdvancedView(RepoAppView):
raise HTTPFound(h.route_path("home"))
def _invalidate_remote_cache(self, delete: bool):
log.debug(f"Invalidating remote cache, for repository: {self.db_repo_name}")
scm_repo = self.db_repo.scm_instance(cache=False)
scm_repo.vcsserver_invalidate_cache(delete=delete)
@LoginRequired()
@HasRepoPermissionAnyDecorator("repository.admin")
@CSRFRequired()

View file

@ -90,10 +90,12 @@ def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
sort=search_sort,
)
search_result_count = search_result["count"]["value"]
formatted_results = Page(
search_result["results"],
page=requested_page,
item_count=search_result["count"],
item_count=search_result_count,
items_per_page=page_limit,
url_maker=url_generator,
)
@ -103,7 +105,7 @@ def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
search_tags = searcher.extract_search_tags(search_query)
if not search_result["error"]:
execution_time = "{} results ({:.4f} seconds)".format(search_result["count"], search_result["runtime"])
execution_time = "{} results ({:.4f} seconds)".format(search_result_count, search_result["runtime"])
elif not errors:
node = schema["search_query"]
errors = [validation_schema.Invalid(node, search_result["error"])]

View file

@ -0,0 +1,2 @@
PYCHARM_DEBUG = "PYCHARM_DEBUG"
PYCHARM_DEBUG_PAUSE_AT_STARTUP = "PYCHARM_DEBUG_PAUSE_AT_STARTUP"

View file

@ -31,6 +31,8 @@ from pyramid.settings import asbool, aslist
from pyramid.httpexceptions import HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound
from pyramid.renderers import render_to_response
from rhodecode.config.constants import PYCHARM_DEBUG
from rhodecode.lib.middleware.pycharm_remote_debugger import PyCharmDebugMiddleware
from rhodecode.model import meta
from rhodecode.config import patches
@ -420,6 +422,16 @@ def includeme(config, auth_resources=None):
config.add_view(error_handler, context=HTTPError)
def wrap_app_in_pycharm_remote_debugger_if_enabled(pyramid_app):
pycharm_debugger_enabled = os.getenv(PYCHARM_DEBUG, "0") == "1"
log.debug(f"Pycharm remote debugger enabled: {pycharm_debugger_enabled}")
if pycharm_debugger_enabled:
return PyCharmDebugMiddleware(pyramid_app)
return pyramid_app
def wrap_app_in_wsgi_middlewares(pyramid_app, config):
"""
Apply outer WSGI middlewares around the application.
@ -429,6 +441,7 @@ def wrap_app_in_wsgi_middlewares(pyramid_app, config):
# enable https redirects based on HTTP_X_URL_SCHEME set by proxy
pyramid_app = HttpsFixup(pyramid_app, settings)
pyramid_app = wrap_app_in_pycharm_remote_debugger_if_enabled(pyramid_app)
pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(pyramid_app, settings)
registry.ae_client = _ae_client

View file

@ -28,16 +28,19 @@ from rhodecode.lib.index.search_utils import normalize_text_for_matching
log = logging.getLogger(__name__)
# leave defaults for backward compat
default_searcher = "rhodecode.lib.index.whoosh"
default_location = "%(here)s/data/index"
default_searcher = "rc_elasticsearch"
ES_VERSION_8 = 8
ES_VERSION_9 = 9
OS_VERSION_2 = 2
# for legacy reasons we keep 8 compat as default
DEFAULT_ES_VERSION = ES_VERSION_8
DEFAULT_OS_VERSION = OS_VERSION_2
ES_VERSION_2 = "2"
ES_VERSION_6 = "6"
# for legacy reasons we keep 2 compat as default
DEFAULT_ES_VERSION = ES_VERSION_2
try:
from rhodecode_tools.lib.fts_index.elasticsearch_engine_6 import ES_CONFIG # pragma: no cover
from rhodecode_tools.lib.fts_index.elasticsearch_engine_8 import ES_CONFIG # pragma: no cover
except ImportError:
log.warning("rhodecode_tools not available, use of full text search is limited")
pass
@ -71,8 +74,8 @@ class BaseSearcher(object):
return " ".join(normalize_text_for_matching(query).split())
@property
def is_es_6(self):
return self.es_version == ES_VERSION_6
def is_es_8(self):
return self.es_version >= ES_VERSION_8
def get_handlers(self):
return {}
@ -140,12 +143,15 @@ def search_config(config, prefix="search."):
def searcher_from_config(config, prefix="search."):
_config = search_config(config, prefix)
if "location" not in _config:
_config["location"] = default_location
if "es_version" not in _config:
# use an old legacy ES version set to 2
_config["es_version"] = "2"
# use an old legacy ES version set to 8
_config["es_version"] = DEFAULT_ES_VERSION
imported = importlib.import_module(_config.get("module", default_searcher))
search_module = _config.get("module", default_searcher)
if search_module == "rhodecode.lib.index.whoosh":
log.warning("rhodecode.lib.index.whoosh module is no longer supported, using default searcher rc_elasticsearch")
search_module = "rc_elasticsearch"
imported = importlib.import_module(search_module)
searcher = imported.Searcher(config=_config)
return searcher

View file

@ -36,28 +36,13 @@ from rhodecode.lib.str_utils import safe_str
log = logging.getLogger(__name__)
try:
# we first try to import from rhodecode tools, fallback to copies if
# we're unable to
from rhodecode_tools.lib.fts_index.whoosh_schema import (
ANALYZER,
FILE_INDEX_NAME,
FILE_SCHEMA,
COMMIT_INDEX_NAME,
COMMIT_SCHEMA,
)
except ImportError:
log.warning(
"rhodecode_tools schema not available, doing a fallback "
"import from `rhodecode.lib.index.whoosh_fallback_schema`"
)
from rhodecode.lib.index.whoosh_fallback_schema import (
ANALYZER,
FILE_INDEX_NAME,
FILE_SCHEMA,
COMMIT_INDEX_NAME,
COMMIT_SCHEMA,
)
from rhodecode.lib.index.whoosh_fallback_schema import (
ANALYZER,
FILE_INDEX_NAME,
FILE_SCHEMA,
COMMIT_INDEX_NAME,
COMMIT_SCHEMA,
)
FORMATTER = HtmlFormatter("span", between='\n<span class="break">...</span>\n')

View file

@ -0,0 +1,37 @@
import os
import logging
import socket
from rhodecode.config.constants import PYCHARM_DEBUG_PAUSE_AT_STARTUP
log = logging.getLogger(__name__)
class PyCharmDebugMiddleware:
def __init__(self, handler):
self.handler = handler
self._start_debugger()
def _start_debugger(self):
try:
import pydevd_pycharm
suspend = os.getenv(PYCHARM_DEBUG_PAUSE_AT_STARTUP, "0") == "1"
host = "host.docker.internal" # assuming that app is running inside docker, and the debug server is on the same machine
pydevd_pycharm.settrace(
host,
suspend=suspend,
stdoutToServer=True,
stderrToServer=True,
)
log.debug("PyCharm debugger attached successfully!")
except ImportError:
log.warning("pydevd_pycharm not installed. Debugging disabled.")
except ConnectionRefusedError:
ip = socket.gethostbyname(host)
log.warning(f"debug server is not running on host[ip]: {host}[{ip}], shutdown remote debugger.")
pydevd_pycharm.stoptrace()
def __call__(self, environ, start_response):
return self.handler(environ, start_response)

View file

@ -235,8 +235,8 @@ class SubversionRepository(base.BaseRepository):
try:
SubversionRepository(path)
return True
except VCSError:
pass
except VCSError as e:
log.warning(f"VCSError: {repr(e)}")
return False
@LazyProperty
@ -307,7 +307,7 @@ class SubversionRepository(base.BaseRepository):
start_raw_id = self._sanitize_commit_id(start_id)
end_raw_id = self._sanitize_commit_id(end_id)
# for empty revisions in SVN
if start_raw_id not in self.commit_ids or end_raw_id not in self.commit_ids:
return base.CollectionGenerator(self, [])

View file

@ -154,53 +154,32 @@
% endfor
<div class="field">
<p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Language examples')}</p>
<pre id="search-help" style="display: none">\
% if c.searcher.name == 'whoosh':
Example filter terms for `Whoosh` search:
query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
<div id="search-help" style="display: none">\
Generate wildcards using '*' character:
"repo_name:vcs*" - search everything starting with 'vcs'
"repo_name:*vcs*" - search for repository containing 'vcs'
% if c.searcher.name == 'elasticsearch':
Optional AND / OR operators in queries
"repo_name:vcs OR repo_name:test"
"owner:test AND repo_name:test*" AND extension:py
Move advanced search is available via ElasticSearch6 backend in EE edition.
% elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
<h3>Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:</h3>
query lang: <a href="${c.searcher.query_lang_doc}">ES ${c.searcher.es_version} Query Language</a></br>
The reserved characters require escaping by `\`: <pre>+ - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /</pre>
</br>
search type: content (File Content)
indexed fields: content
# search for `fix` string in all files
fix
search type: commit (Commit message)
indexed fields: message
search type: path (File name)
indexed fields: path
% else:
Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
% for handler in c.searcher.get_handlers().values():
search type: ${handler.search_type_label}
*indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
<h4>Search type: ${handler.search_type_label}</h4>
<strong>indexed fields:</strong>
<pre>${', '.join( [('\n' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}</pre>
<pre>
% for entry in handler.es_6_example_queries:
${entry.rstrip()}
${h.textwrap.dedent(entry.rstrip())}
% endfor
</pre>
% endfor
% endif
</pre>
</div>
</div>
<div class="field">${c.runtime}</div>

View file

@ -1,8 +1,39 @@
<%namespace name="search" file="/search/search.mako"/>
<%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
<%def name="highlight_text_file(file_path, full_file_content, hl_file_content, mimetype, url, use_hl_filter)">
<%
matching_lines = []
shown_matching_lines = 0
if c.searcher.is_es_8:
# use empty terms so we default to markers usage
total_lines, matching_lines = h.get_matching_line_offsets(hl_file_content, terms=None)
lines_of_interest = set()
for line_number in matching_lines:
if len(lines_of_interest) < max_lines:
lines_of_interest |= set(range(
max(line_number - line_context, 0),
min(line_number + line_context, total_lines + 1)))
shown_matching_lines += 1
has_matched_content = len(lines_of_interest) >= 1
%>
% if has_matched_content:
${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
<%
lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
html_formatter = h.SearchContentCodeHtmlFormatter(
linenos=True,
cssclass="code-highlight",
url=match_file_url,
query_terms=terms,
only_line_numbers=lines_of_interest
)
%>
${h.code_highlight(hl_file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
% else:
${_('No content matched')} <br/>
% endif
@ -20,41 +51,15 @@
%for entry in c.formatted_results:
<%
file_content = entry['content_highlight'] or entry['content']
mimetype = entry.get('mimetype')
hl_file_content = entry['content_highlight']
full_file_content = entry['content']
filepath = entry.get('path')
mimetype = entry.get('mimetype')
max_lines = h.safe_int(request.GET.get('max_lines', '10'))
line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
line_context = h.safe_int(request.GET.get('line_context', '3'))
match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
terms = c.cur_query
if c.searcher.is_es_6:
# use empty terms so we default to markers usage
total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
else:
total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
shown_matching_lines = 0
lines_of_interest = set()
for line_number in matching_lines:
if len(lines_of_interest) < max_lines:
lines_of_interest |= set(range(
max(line_number - line_context, 0),
min(line_number + line_context, total_lines + 1)))
shown_matching_lines += 1
lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
html_formatter = h.SearchContentCodeHtmlFormatter(
linenos=True,
cssclass="code-highlight",
url=match_file_url,
query_terms=terms,
only_line_numbers=lines_of_interest
)
has_matched_content = len(lines_of_interest) >= 1
%>
## search results are additionally filtered, and this check is just a safe gate
% if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
@ -75,7 +80,7 @@
<div class="stats-info">
<span class="stats-first-item">
${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
(${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
##(${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
</span>
<span>
% if entry.get('size'):
@ -112,7 +117,7 @@
<a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
</span>
% endif
## hiden if in repo view
## hidden if in repo view
% if not c.repo_name:
<span class="tag tag8">
${search.repo_icon(repo_type)}
@ -125,17 +130,14 @@
<div class="clear-fix"></div>
</div>
<div class="code-body search-code-body clear-fix">
${highlight_text_file(
has_matched_content=has_matched_content,
file_content=file_content,
lexer=lexer,
html_formatter=html_formatter,
matching_lines=matching_lines,
shown_matching_lines=shown_matching_lines,
file_path=file_path,
full_file_content=full_file_content,
hl_file_content=hl_file_content,
mimetype=mimetype,
url=match_file_url,
use_hl_filter=c.searcher.is_es_6
use_hl_filter=c.searcher.is_es_8
)}
</div>