diff --git a/.bumpversion.cfg b/.bumpversion.cfg --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,6 +1,5 @@ [bumpversion] -current_version = 4.24.1 +current_version = 4.25.0 message = release: Bump version {current_version} to {new_version} [bumpversion:file:rhodecode/VERSION] - diff --git a/.release.cfg b/.release.cfg --- a/.release.cfg +++ b/.release.cfg @@ -5,25 +5,20 @@ done = false done = true [task:rc_tools_pinned] -done = true [task:fixes_on_stable] -done = true [task:pip2nix_generated] -done = true [task:changelog_updated] -done = true [task:generate_api_docs] -done = true + +[task:updated_translation] [release] -state = prepared -version = 4.24.1 - -[task:updated_translation] +state = in_progress +version = 4.25.0 [task:generate_js_routes] diff --git a/docs/api/methods/store-methods.rst b/docs/api/methods/store-methods.rst --- a/docs/api/methods/store-methods.rst +++ b/docs/api/methods/store-methods.rst @@ -130,6 +130,24 @@ file_store_get_info (EE only) error : null +file_store_delete (EE only) +--------------------------- + +.. py:function:: file_store_delete(apiuser, store_fid) + + Delete an artifact based on the secret uuid. + + Example output: + + .. code-block:: bash + + id : + result: { + "artifact" : {"uid": "some uid", "removed": true} + } + error : null + + file_store_add_metadata (EE only) --------------------------------- diff --git a/docs/release-notes/release-notes-4.25.0.rst b/docs/release-notes/release-notes-4.25.0.rst new file mode 100644 --- /dev/null +++ b/docs/release-notes/release-notes-4.25.0.rst @@ -0,0 +1,76 @@ +|RCE| 4.25.0 |RNS| +------------------ + +Release Date +^^^^^^^^^^^^ + +- 2021-04-02 + + +New Features +^^^^^^^^^^^^ + +- SSH: allow clone by ID via SSH operations. +- Artifacts: added an admin panel to manage artifacts. +- Redmine: added option to add note to a ticket without changing its status in Redmine integration. + + +General +^^^^^^^ + +- Git: change lookups logic. Prioritize reference names over numerical ids. + Numerical ids are supported as a fallback if ref matching is unsuccessful. +- Permissions: changed fork permission help text to reflect the actual state on how it works. +- Permissions: flush permissions on owner changes for repo and repo groups. This + would fix problems when owner of repository changes then the new owner lacked permissions + until cache expired. +- Artifacts: added API function to remove artifacts. +- Archives: use a special name for non-hashed archives to fix caching issues. +- Packaging: fixed few packages requirements for a proper builds. +- Packaging: fix rhodecode-tools for docker builds. +- Packaging: fixed some problem after latest setuptools-scm release. +- Packaging: added setuptools-scm to packages for build. +- Packaging: fix jira package for reproducible builds. +- Packaging: fix zipp package patches. + + +Security +^^^^^^^^ + +- Comments: forbid removal of comments by anyone except the owners. + Previously admins of a repository could remove them if they would construct a special url with data. +- Pull requests: fixed some xss problems when a deleted file with special characters were commented on. + + +Performance +^^^^^^^^^^^ + +- License: skip channelstream connect on license checks logic to reduce calls handling times. +- Core: optimize some calls to skip license/scm detection on them. Each license check is expensive + and we don't need them on each call. + + +Fixes +^^^^^ + +- Branch-permissions: fixed ce view. Fixes #5656 +- Feed: fix errors on feed access of empty repositories. +- Archives: if implicit ref name was used (e.g master) to obtain archive, we now + redirect to explicit commit sha so we can have the proper caching for references names. +- rcextensions: fixed pre-files extractor return code support. +- Svn: fix subprocess problems on some of the calls for file checking. +- Pull requests: fixed multiple repetitions of referenced tickets in pull requests summary sidebar. +- Maintenance: fixed bad routes def +- clone-uri: fixed the problems with key mismatch that caused errors on summary page. +- Largefiles: added fix for downloading largefiles which had no extension in file name. +- Compare: fix referenced commits bug. +- Git: fix for unicode branches + + +Upgrade notes +^^^^^^^^^^^^^ + +- Scheduled release 4.25.0. + + + 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,7 @@ Release Notes .. toctree:: :maxdepth: 1 + release-notes-4.25.0.rst release-notes-4.24.1.rst release-notes-4.24.0.rst release-notes-4.23.2.rst diff --git a/pkgs/patches/channelstream/setuptools.patch b/pkgs/patches/channelstream/setuptools.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/channelstream/setuptools.patch @@ -0,0 +1,13 @@ +diff -rup channelstream-0.6.14-orig/setup.py channelstream-0.6.14/setup.py + +--- channelstream-0.6.14/setup-orig.py 2021-03-11 12:34:45.000000000 +0100 ++++ channelstream-0.6.14/setup.py 2021-03-11 12:34:56.000000000 +0100 +@@ -52,7 +52,7 @@ setup( + include_package_data=True, + install_requires=requires, + python_requires=">=2.7", +- setup_requires=["pytest-runner"], ++ setup_requires=["pytest-runner==5.1.0"], + extras_require={ + "dev": ["coverage", "pytest", "pyramid", "tox", "mock", "webtest"], + "lint": ["black"], diff --git a/pkgs/patches/configparser/pyproject.patch b/pkgs/patches/configparser/pyproject.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/configparser/pyproject.patch @@ -0,0 +1,10 @@ +diff -rup configparser-4.0.2-orig/pyproject.toml configparser-4.0.2/pyproject.toml +--- configparser-4.0.2-orig/pyproject.toml 2021-03-22 21:28:11.000000000 +0100 ++++ configparser-4.0.2/pyproject.toml 2021-03-22 21:28:11.000000000 +0100 +@@ -1,5 +1,5 @@ + [build-system] +-requires = ["setuptools>=40.7", "wheel", "setuptools_scm>=1.15"] ++requires = ["setuptools<=42.0", "wheel", "setuptools_scm<6.0.0"] + build-backend = "setuptools.build_meta" + + [tool.black] diff --git a/pkgs/patches/importlib_metadata/pyproject.patch b/pkgs/patches/importlib_metadata/pyproject.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/importlib_metadata/pyproject.patch @@ -0,0 +1,7 @@ +diff -rup importlib-metadata-1.6.0-orig/yproject.toml importlib-metadata-1.6.0/pyproject.toml +--- importlib-metadata-1.6.0-orig/yproject.toml 2021-03-22 22:10:33.000000000 +0100 ++++ importlib-metadata-1.6.0/pyproject.toml 2021-03-22 22:11:09.000000000 +0100 +@@ -1,3 +1,3 @@ + [build-system] +-requires = ["setuptools>=30.3", "wheel", "setuptools_scm"] ++requires = ["setuptools<42.0", "wheel", "setuptools_scm<6.0.0"] diff --git a/pkgs/patches/pyramid_apispec/setuptools.patch b/pkgs/patches/pyramid_apispec/setuptools.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/pyramid_apispec/setuptools.patch @@ -0,0 +1,12 @@ +diff -rup pyramid-apispec-0.3.2-orig/setup.py pyramid-apispec-0.3.2/setup.py +--- pyramid-apispec-0.3.2-orig/setup.py 2021-03-11 11:19:26.000000000 +0100 ++++ pyramid-apispec-0.3.2/setup.py 2021-03-11 11:19:51.000000000 +0100 +@@ -44,7 +44,7 @@ setup( + packages=find_packages(exclude=["contrib", "docs", "tests"]), + package_data={"pyramid_apispec": ["static/*.*"], "": ["LICENSE"]}, + install_requires=["apispec[yaml]==1.0.0"], +- setup_requires=["pytest-runner"], ++ setup_requires=["pytest-runner==5.1"], + extras_require={ + "dev": ["coverage", "pytest", "pyramid", "tox", "webtest"], + "demo": ["marshmallow==2.15.3", "pyramid", "apispec", "webtest"], \ No newline at end of file diff --git a/pkgs/patches/pytest/setuptools.patch b/pkgs/patches/pytest/setuptools.patch --- a/pkgs/patches/pytest/setuptools.patch +++ b/pkgs/patches/pytest/setuptools.patch @@ -6,7 +6,7 @@ diff -rup pytest-4.6.5-orig/setup.py pyt setup( use_scm_version={"write_to": "src/_pytest/_version.py"}, - setup_requires=["setuptools-scm", "setuptools>=40.0"], -+ setup_requires=["setuptools-scm", "setuptools<=42.0"], ++ setup_requires=["setuptools-scm<6.0.0", "setuptools<=42.0"], package_dir={"": "src"}, # fmt: off extras_require={ \ No newline at end of file diff --git a/pkgs/patches/rhodecode_tools/setuptools.patch b/pkgs/patches/rhodecode_tools/setuptools.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/rhodecode_tools/setuptools.patch @@ -0,0 +1,12 @@ +diff -rup rhodecode-tools-1.4.0-orig/setup.py rhodecode-tools-1.4.0/setup.py +--- rhodecode-tools-1.4.0/setup-orig.py 2021-03-11 12:34:45.000000000 +0100 ++++ rhodecode-tools-1.4.0/setup.py 2021-03-11 12:34:56.000000000 +0100 +@@ -69,7 +69,7 @@ def _get_requirements(req_filename, excl + + + # requirements extract +-setup_requirements = ['pytest-runner'] ++setup_requirements = ['pytest-runner==5.1.0'] + install_requirements = _get_requirements( + 'requirements.txt', exclude=['setuptools']) + test_requirements = _get_requirements('requirements_test.txt') \ No newline at end of file diff --git a/pkgs/patches/zipp/pyproject.patch b/pkgs/patches/zipp/pyproject.patch new file mode 100644 --- /dev/null +++ b/pkgs/patches/zipp/pyproject.patch @@ -0,0 +1,10 @@ +diff -rup zip-1.2.0-orig/pyproject.toml zip-1.2.0/pyproject.toml +--- zip-1.2.0-orig/pyproject.toml 2021-03-23 10:55:37.000000000 +0100 ++++ zip-1.2.0/pyproject.toml 2021-03-23 10:56:05.000000000 +0100 +@@ -1,5 +1,5 @@ + [build-system] +-requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"] ++requires = ["setuptools<42.0", "wheel", "setuptools_scm<6.0.0"] + build-backend = "setuptools.build_meta" + + [tool.black] diff --git a/pkgs/python-packages-overrides.nix b/pkgs/python-packages-overrides.nix --- a/pkgs/python-packages-overrides.nix +++ b/pkgs/python-packages-overrides.nix @@ -280,6 +280,72 @@ self: super: { ]; }); + "pytest-runner" = super."pytest-runner".override (attrs: { + propagatedBuildInputs = [ + self."setuptools-scm" + ]; + }); + + "py" = super."py".override (attrs: { + propagatedBuildInputs = [ + self."setuptools-scm" + ]; + }); + + "python-dateutil" = super."python-dateutil".override (attrs: { + propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ + self."setuptools-scm" + ]; + }); + + "configparser" = super."configparser".override (attrs: { + patches = [ + ./patches/configparser/pyproject.patch + ]; + propagatedBuildInputs = [ + self."setuptools-scm" + ]; + }); + + "importlib-metadata" = super."importlib-metadata".override (attrs: { + + patches = [ + ./patches/importlib_metadata/pyproject.patch + ]; + + propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ + self."setuptools-scm" + ]; + + }); + + "zipp" = super."zipp".override (attrs: { + patches = [ + ./patches/zipp/pyproject.patch + ]; + propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ + self."setuptools-scm" + ]; + }); + + "pyramid-apispec" = super."pyramid-apispec".override (attrs: { + patches = [ + ./patches/pyramid_apispec/setuptools.patch + ]; + }); + + "channelstream" = super."channelstream".override (attrs: { + patches = [ + ./patches/channelstream/setuptools.patch + ]; + }); + + "rhodecode-tools" = super."rhodecode-tools".override (attrs: { + patches = [ + ./patches/rhodecode_tools/setuptools.patch + ]; + }); + # Avoid that base packages screw up the build process inherit (basePythonPackages) setuptools; diff --git a/pkgs/python-packages.nix b/pkgs/python-packages.nix --- a/pkgs/python-packages.nix +++ b/pkgs/python-packages.nix @@ -1883,7 +1883,7 @@ self: super: { }; }; "rhodecode-enterprise-ce" = super.buildPythonPackage { - name = "rhodecode-enterprise-ce-4.24.1"; + name = "rhodecode-enterprise-ce-4.25.0"; buildInputs = [ self."pytest" self."py" @@ -2092,6 +2092,17 @@ self: super: { license = [ pkgs.lib.licenses.mit ]; }; }; + "setuptools-scm" = super.buildPythonPackage { + name = "setuptools-scm-3.5.0"; + doCheck = false; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/b2/f7/60a645aae001a2e06cf4b8db2fba9d9f36b8fd378f10647e3e218b61b74b/setuptools_scm-3.5.0.tar.gz"; + sha256 = "5bdf21a05792903cafe7ae0c9501182ab52497614fa6b1750d9dbae7b60c1a87"; + }; + meta = { + license = [ pkgs.lib.licenses.psfl ]; + }; + }; "simplegeneric" = super.buildPythonPackage { name = "simplegeneric-0.8.1"; doCheck = false; diff --git a/rhodecode/VERSION b/rhodecode/VERSION --- a/rhodecode/VERSION +++ b/rhodecode/VERSION @@ -1,1 +1,1 @@ -4.24.1 \ No newline at end of file +4.25.0 \ No newline at end of file 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 @@ -1212,7 +1212,7 @@ def fork_repo(request, apiuser, repoid, validate_repo_permissions(apiuser, repoid, repo, _perms) # check if the regular user has at least fork permissions as well - if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser): + if not HasPermissionAnyApi(PermissionModel.FORKING_ENABLED)(user=apiuser): raise JSONRPCForbidden() # check if user can set owner parameter diff --git a/rhodecode/apps/file_store/backends/local_store.py b/rhodecode/apps/file_store/backends/local_store.py --- a/rhodecode/apps/file_store/backends/local_store.py +++ b/rhodecode/apps/file_store/backends/local_store.py @@ -255,7 +255,7 @@ class LocalFileStorage(object): return filename, metadata - def get_metadata(self, filename): + def get_metadata(self, filename, ignore_missing=False): """ Reads JSON stored metadata for a file @@ -264,6 +264,7 @@ class LocalFileStorage(object): """ filename = self.store_path(filename) filename_meta = filename + '.meta' - + if ignore_missing and not os.path.isfile(filename_meta): + return {} with open(filename_meta, "rb") as source_meta: return json.loads(source_meta.read()) 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 @@ -932,8 +932,8 @@ def includeme(config): name='edit_repo_perms_branch', pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True) config.add_view( - RepoBranchesView, - attr='branches', + RepoSettingsBranchPermissionsView, + attr='branch_permissions', route_name='edit_repo_perms_branch', request_method='GET', renderer='rhodecode:templates/admin/repos/repo_edit.mako') @@ -950,8 +950,8 @@ def includeme(config): config.add_view( RepoMaintenanceView, attr='repo_maintenance', - route_name='edit_repo_maintenance_execute', request_method='GET', - renderer='json', xhr=True) + route_name='edit_repo_maintenance', request_method='GET', + renderer='rhodecode:templates/admin/repos/repo_edit.mako') config.add_route( name='edit_repo_maintenance_execute', @@ -959,8 +959,8 @@ def includeme(config): config.add_view( RepoMaintenanceView, attr='repo_maintenance_execute', - route_name='edit_repo_maintenance', request_method='GET', - renderer='rhodecode:templates/admin/repos/repo_edit.mako') + route_name='edit_repo_maintenance_execute', request_method='GET', + renderer='json', xhr=True) # Fields config.add_route( 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 @@ -542,6 +542,28 @@ class TestRepositoryArchival(object): for header in headers: assert header in response.headers.items() + def test_archival_no_hash(self, backend): + backend.enable_downloads() + commit = backend.repo.get_commit(commit_idx=173) + for a_type, content_type, extension in settings.ARCHIVE_SPECS: + + short = 'plain' + extension + fname = commit.raw_id + extension + filename = '%s-%s' % (backend.repo_name, short) + response = self.app.get( + route_path('repo_archivefile', + repo_name=backend.repo_name, + fname=fname, params={'with_hash': 0})) + + assert response.status == '200 OK' + headers = [ + ('Content-Disposition', 'attachment; filename=%s' % filename), + ('Content-Type', '%s' % content_type), + ] + + for header in headers: + assert header in response.headers.items() + @pytest.mark.parametrize('arch_ext',[ 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar']) def test_archival_wrong_ext(self, backend, arch_ext): diff --git a/rhodecode/apps/repository/tests/test_repo_maintainance.py b/rhodecode/apps/repository/tests/test_repo_maintainance.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/repository/tests/test_repo_maintainance.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2020 RhodeCode GmbH +# +# This 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.lib.utils2 import str2bool +from rhodecode.lib.vcs.exceptions import RepositoryRequirementError +from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User +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 + +fixture = Fixture() + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'edit_repo_maintenance': '/{repo_name}/settings/maintenance', + 'edit_repo_maintenance_execute': '/{repo_name}/settings/maintenance/execute', + + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +def _get_permission_for_user(user, repo): + perm = UserRepoToPerm.query()\ + .filter(UserRepoToPerm.repository == + Repository.get_by_repo_name(repo))\ + .filter(UserRepoToPerm.user == User.get_by_username(user))\ + .all() + return perm + + +@pytest.mark.usefixtures('autologin_user', 'app') +class TestAdminRepoMaintenance(object): + @pytest.mark.parametrize('urlname', [ + 'edit_repo_maintenance', + ]) + def test_show_page(self, urlname, app, backend): + app.get(route_path(urlname, repo_name=backend.repo_name), status=200) + + def test_execute_maintenance_for_repo_hg(self, app, backend_hg, autologin_user, xhr_header): + repo_name = backend_hg.repo_name + + response = app.get( + route_path('edit_repo_maintenance_execute', + repo_name=repo_name,), + extra_environ=xhr_header) + + assert "HG Verify repo" in ''.join(response.json) 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 @@ -34,7 +34,7 @@ from rhodecode.lib.auth import ( from rhodecode.lib.ext_json import json from rhodecode.lib.graphmod import _colored, _dagwalker from rhodecode.lib.helpers import RepoPage -from rhodecode.lib.utils2 import safe_int, safe_str, str2bool +from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode from rhodecode.lib.vcs.exceptions import ( RepositoryError, CommitDoesNotExistError, CommitError, NodeDoesNotExistError, EmptyRepositoryError) @@ -110,7 +110,7 @@ class RepoChangelogView(RepoAppView): def _check_if_valid_branch(self, branch_name, repo_name, f_path): if branch_name not in self.rhodecode_vcs_repo.branches_all: - h.flash('Branch {} is not found.'.format(h.escape(branch_name)), + h.flash(u'Branch {} is not found.'.format(h.escape(safe_unicode(branch_name))), category='warning') redirect_url = h.route_path( 'repo_commits_file', repo_name=repo_name, 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 @@ -674,6 +674,10 @@ class RepoCommitsView(RepoAppView): is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id comment_repo_admin = is_repo_admin and is_repo_comment + if comment.draft and not comment_owner: + # We never allow to delete draft comments for other than owners + raise HTTPNotFound() + if super_admin or comment_owner or comment_repo_admin: CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user) Session().commit() 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 @@ -104,6 +104,9 @@ class RepoFeedView(RepoAppView): def _get_commits(self): pre_load = ['author', 'branch', 'date', 'message', 'parents'] + if self.rhodecode_vcs_repo.is_empty(): + return [] + collection = self.rhodecode_vcs_repo.get_commits( branch_name=None, show_hidden=False, pre_load=pre_load, translate_tags=False) @@ -137,6 +140,7 @@ class RepoFeedView(RepoAppView): language=self.language, ttl=self.ttl ) + for commit in reversed(self._get_commits()): date = self._set_timezone(commit.date) feed.add_item( 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 @@ -325,17 +325,18 @@ class RepoFilesView(RepoAppView): return lf_enabled - def _get_archive_name(self, db_repo_name, commit_sha, ext, subrepos=False, path_sha=''): + def _get_archive_name(self, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True): # original backward compat name of archive clean_name = safe_str(db_repo_name.replace('/', '_')) # e.g vcsserver.zip # e.g vcsserver-abcdefgh.zip # e.g vcsserver-abcdefgh-defghijk.zip - archive_name = '{}{}{}{}{}'.format( + archive_name = '{}{}{}{}{}{}'.format( clean_name, '-sub' if subrepos else '', commit_sha, + '-{}'.format('plain') if not with_hash else '', '-{}'.format(path_sha) if path_sha else '', ext) return archive_name @@ -372,6 +373,11 @@ class RepoFilesView(RepoAppView): except EmptyRepositoryError: 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='{}{}'.format(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 except Exception: @@ -385,7 +391,7 @@ class RepoFilesView(RepoAppView): # used for cache etc archive_name = self._get_archive_name( self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, - path_sha=path_sha) + path_sha=path_sha, with_hash=with_hash) if not with_hash: short_sha = '' @@ -394,7 +400,7 @@ class RepoFilesView(RepoAppView): # what end client gets served response_archive_name = self._get_archive_name( self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos, - path_sha=path_sha) + path_sha=path_sha, with_hash=with_hash) # remove extension from our archive directory name archive_dir_name = response_archive_name[:-len(ext)] @@ -404,9 +410,10 @@ class RepoFilesView(RepoAppView): cached_archive_path = None if archive_cache_enabled: - # check if we it's ok to write + # check if we it's ok to write, and re-create the archive cache if not os.path.isdir(CONFIG['archive_cache_dir']): os.makedirs(CONFIG['archive_cache_dir']) + cached_archive_path = os.path.join( CONFIG['archive_cache_dir'], archive_name) if os.path.isfile(cached_archive_path): 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 @@ -165,7 +165,7 @@ class RepoForksView(RepoAppView, DataGri @LoginRequired() @NotAnonymous() - @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') + @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED) @HasRepoPermissionAnyDecorator( 'repository.read', 'repository.write', 'repository.admin') def repo_fork_new(self): @@ -191,7 +191,7 @@ class RepoForksView(RepoAppView, DataGri @LoginRequired() @NotAnonymous() - @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') + @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED) @HasRepoPermissionAnyDecorator( 'repository.read', 'repository.write', 'repository.admin') @CSRFRequired() 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 @@ -1748,6 +1748,10 @@ class RepoPullRequestsView(RepoAppView, is_repo_comment = comment.repo.repo_name == self.db_repo_name comment_repo_admin = is_repo_admin and is_repo_comment + if comment.draft and not comment_owner: + # We never allow to delete draft comments for other than owners + raise HTTPNotFound() + if super_admin or comment_owner or comment_repo_admin: old_calculated_status = comment.pull_request.calculated_review_status() CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user) 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 @@ -89,6 +89,14 @@ class SshWrapper(object): return conn + def maybe_translate_repo_uid(self, repo_name): + if repo_name.startswith('_'): + from rhodecode.model.repo import RepoModel + by_id_match = RepoModel().get_repo_by_id(repo_name) + if by_id_match: + repo_name = by_id_match.repo_name + return repo_name + def get_repo_details(self, mode): vcs_type = mode if mode in ['svn', 'hg', 'git'] else None repo_name = None @@ -97,14 +105,14 @@ class SshWrapper(object): hg_match = re.match(hg_pattern, self.command) if hg_match is not None: vcs_type = 'hg' - repo_name = hg_match.group(1).strip('/') + repo_name = self.maybe_translate_repo_uid(hg_match.group(1).strip('/')) return vcs_type, repo_name, mode git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$' git_match = re.match(git_pattern, self.command) if git_match is not None: vcs_type = 'git' - repo_name = git_match.group(2).strip('/') + repo_name = self.maybe_translate_repo_uid(git_match.group(2).strip('/')) mode = git_match.group(1) return vcs_type, repo_name, mode 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 @@ -19,6 +19,8 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ import json +import os + import mock import pytest @@ -107,6 +109,8 @@ class TestGitServer(object): def test_run_returns_executes_command(self, git_server): 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'): 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 @@ -108,6 +108,7 @@ class TestMercurialServer(object): def test_run_returns_executes_command(self, hg_server): server = hg_server.create() from rhodecode.apps.ssh_support.lib.backends.hg import MercurialTunnelWrapper + os.environ['SSH_CLIENT'] = '127.0.0.1' with mock.patch.object(MercurialTunnelWrapper, 'create_hooks_env') as _patch: _patch.return_value = 0 with mock.patch.object(MercurialTunnelWrapper, 'command', return_value='date'): 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 @@ -17,7 +17,7 @@ # 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 @@ -174,6 +174,7 @@ class TestSubversionServer(object): def test_run_returns_executes_command(self, svn_server): 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', return_value={'url': 'http://server/test-svn'}): 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 @@ -32,6 +32,7 @@ import json from rhodecode.lib import diffs from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff from rhodecode.lib.vcs.backends.git.diff import GitDiff +from vcsserver.utils import safe_int def get_svn_files(repo, vcs_repo, refs): @@ -74,7 +75,7 @@ def get_svn_files(repo, vcs_repo, refs): # skip dirs continue - parsed_entry['file_size'] = int(stdout.strip()) + parsed_entry['file_size'] = safe_int(stdout.strip()) or 0 files.append(parsed_entry) diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py --- a/rhodecode/lib/auth.py +++ b/rhodecode/lib/auth.py @@ -524,8 +524,10 @@ class PermissionCalculator(object): # In case we want to extend this list we should make sure # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions + from rhodecode.model.permission import PermissionModel + _configurable = frozenset([ - 'hg.fork.none', 'hg.fork.repository', + PermissionModel.FORKING_DISABLED, PermissionModel.FORKING_ENABLED, 'hg.create.none', 'hg.create.repository', 'hg.usergroup.create.false', 'hg.usergroup.create.true', 'hg.repogroup.create.false', 'hg.repogroup.create.true', diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -25,6 +25,7 @@ Consists of functions to typically be us available to Controllers. This module is available to both as 'h'. """ import base64 +import collections import os import random @@ -1733,7 +1734,7 @@ def process_patterns(text_string, repo_n def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None, - issues_container=None, error_container=None): + issues_container_callback=None, error_container=None): """ Parses given text message and makes proper links. issues are linked to given issue-server, and rest is a commit link @@ -1756,8 +1757,9 @@ def urlify_commit_message(commit_text, r new_text, issues, errors = process_patterns( new_text, repository or '', active_entries=active_pattern_entries) - if issues_container is not None: - issues_container.extend(issues) + if issues_container_callback is not None: + for issue in issues: + issues_container_callback(issue) if error_container is not None: error_container.extend(errors) @@ -1802,7 +1804,7 @@ def renderer_from_filename(filename, exc def render(source, renderer='rst', mentions=False, relative_urls=None, - repo_name=None, active_pattern_entries=None, issues_container=None): + repo_name=None, active_pattern_entries=None, issues_container_callback=None): def maybe_convert_relative_links(html_source): if relative_urls: @@ -1819,8 +1821,9 @@ def render(source, renderer='rst', menti source, issues, errors = process_patterns( source, repo_name, link_format='rst', active_entries=active_pattern_entries) - if issues_container is not None: - issues_container.extend(issues) + if issues_container_callback is not None: + for issue in issues: + issues_container_callback(issue) return literal( '
%s
' % @@ -1833,8 +1836,10 @@ def render(source, renderer='rst', menti source, issues, errors = process_patterns( source, repo_name, link_format='markdown', active_entries=active_pattern_entries) - if issues_container is not None: - issues_container.extend(issues) + if issues_container_callback is not None: + for issue in issues: + issues_container_callback(issue) + return literal( '
%s
' % @@ -2115,3 +2120,29 @@ def is_active(menu_entry, selected): if selected in menu_entry: return "active" + + +class IssuesRegistry(object): + """ + issue_registry = IssuesRegistry() + some_func(issues_callback=issues_registry(...)) + """ + + def __init__(self): + self.issues = [] + self.unique_issues = collections.defaultdict(lambda: []) + + def __call__(self, commit_dict=None): + def callback(issue): + if commit_dict and issue: + issue['commit'] = commit_dict + self.issues.append(issue) + self.unique_issues[issue['id']].append(issue) + return callback + + def get_issues(self): + return self.issues + + @property + def issues_unique_count(self): + return len(set(i['id'] for i in self.issues)) 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 @@ -161,15 +161,28 @@ def detect_vcs_request(environ, backends # List of path views first chunk we don't do any checks white_list = [ # e.g /_file_store/download - '_file_store' + '_file_store', + + # static files no detection + '_static', + + # full channelstream connect should be VCS skipped + '_admin/channelstream/connect', ] path_info = environ['PATH_INFO'] - if get_path_elem(path_info) in white_list: + path_elem = get_path_elem(path_info) + + if path_elem in white_list: log.debug('path `%s` in whitelist, skipping...', path_info) return handler + path_url = path_info.lstrip('/') + if path_url in white_list: + log.debug('full url path `%s` in whitelist, skipping...', path_url) + return handler + if VCS_TYPE_KEY in environ: raw_type = environ[VCS_TYPE_KEY] if raw_type == VCS_TYPE_SKIP: @@ -181,7 +194,7 @@ def detect_vcs_request(environ, backends log.debug('got handler:%s from environ', handler) if not handler: - log.debug('request start: checking if request is of VCS type in order: %s', backends) + log.debug('request start: checking if request for `%s` is of VCS type in order: %s', path_elem, backends) for vcs_type in backends: vcs_check, _handler = checks[vcs_type] if vcs_check(environ): diff --git a/rhodecode/lib/utils2.py b/rhodecode/lib/utils2.py --- a/rhodecode/lib/utils2.py +++ b/rhodecode/lib/utils2.py @@ -594,6 +594,9 @@ def credentials_filter(uri): :param uri: """ import urlobject + if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue): + return 'InvalidDecryptionKey' + url_obj = urlobject.URLObject(cleaned_uri(uri)) url_obj = url_obj.without_password().without_username() @@ -655,7 +658,7 @@ def get_clone_url(request, uri_tmpl, rep def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None, - maybe_unreachable=False): + maybe_unreachable=False, reference_obj=None): """ Safe version of get_commit if this commit doesn't exists for a repository it returns a Dummy one instead @@ -665,6 +668,7 @@ def get_commit_safe(repo, commit_id=None :param commit_idx: numeric commit index :param pre_load: optional list of commit attributes to load :param maybe_unreachable: translate unreachable commits on git repos + :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123" """ # TODO(skreft): remove these circular imports from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit @@ -676,7 +680,7 @@ def get_commit_safe(repo, commit_id=None try: commit = repo.get_commit( commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load, - maybe_unreachable=maybe_unreachable) + maybe_unreachable=maybe_unreachable, reference_obj=reference_obj) except (RepositoryError, LookupError): commit = EmptyCommit() return commit 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 @@ -72,6 +72,10 @@ class Reference(_Reference): if self.type == 'book': return self.name + @property + def to_unicode(self): + return reference_to_unicode(self) + def unicode_to_reference(raw): """ @@ -483,7 +487,7 @@ class BaseRepository(object): self._is_empty = False def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, - translate_tag=None, maybe_unreachable=False): + 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. 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 @@ -98,7 +98,7 @@ class GitCommit(base.BaseCommit): elif attr == "parents": value = self._make_commits(value) elif attr == "branch": - value = value[0] if value else None + value = self._set_branch(value) self.__dict__[attr] = value @LazyProperty @@ -156,13 +156,15 @@ class GitCommit(base.BaseCommit): branches.append(name) return branches + def _set_branch(self, branches): + if branches: + # actually commit can have multiple branches in git + return safe_unicode(branches[0]) + @LazyProperty def branch(self): branches = self._remote.branch(self.raw_id) - - if branches: - # actually commit can have multiple branches in git - return safe_unicode(branches[0]) + return self._set_branch(branches) def _get_tree_id_for_path(self, path): path = safe_str(path) 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 @@ -228,7 +228,8 @@ class GitRepository(BaseRepository): return [] return output.splitlines() - def _lookup_commit(self, commit_id_or_idx, translate_tag=True, maybe_unreachable=False): + def _lookup_commit(self, commit_id_or_idx, translate_tag=True, maybe_unreachable=False, reference_obj=None): + def is_null(value): return len(value) == commit_id_or_idx.count('0') @@ -239,21 +240,34 @@ class GitRepository(BaseRepository): *map(safe_str, [commit_id_or_idx, self.name])) is_bstr = isinstance(commit_id_or_idx, (str, unicode)) - if ((is_bstr and commit_id_or_idx.isdigit() and len(commit_id_or_idx) < 12) - or isinstance(commit_id_or_idx, int) or is_null(commit_id_or_idx)): - try: - commit_id_or_idx = self.commit_ids[int(commit_id_or_idx)] - except Exception: - raise CommitDoesNotExistError(commit_missing_err) + is_branch = reference_obj and reference_obj.branch - elif is_bstr: - # Need to call remote to translate id for tagging scenario + lookup_ok = False + if is_bstr: + # Need to call remote to translate id for tagging scenarios, + # or branch that are numeric try: remote_data = self._remote.get_object(commit_id_or_idx, maybe_unreachable=maybe_unreachable) commit_id_or_idx = remote_data["commit_id"] + lookup_ok = True except (CommitDoesNotExistError,): - raise CommitDoesNotExistError(commit_missing_err) + lookup_ok = False + + if lookup_ok is False: + is_numeric_idx = \ + (is_bstr and commit_id_or_idx.isdigit() and len(commit_id_or_idx) < 12) \ + or isinstance(commit_id_or_idx, int) + if not is_branch and (is_numeric_idx or is_null(commit_id_or_idx)): + try: + commit_id_or_idx = self.commit_ids[int(commit_id_or_idx)] + lookup_ok = True + except Exception: + raise CommitDoesNotExistError(commit_missing_err) + + # we failed regular lookup, and by integer number lookup + if lookup_ok is False: + raise CommitDoesNotExistError(commit_missing_err) # Ensure we return full id if not SHA_PATTERN.match(str(commit_id_or_idx)): @@ -413,11 +427,12 @@ class GitRepository(BaseRepository): return def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, - translate_tag=True, maybe_unreachable=False): + translate_tag=True, maybe_unreachable=False, reference_obj=None): """ Returns `GitCommit` object representing commit from git repository at the given `commit_id` or head (most recent commit) if None given. """ + if self.is_empty(): raise EmptyRepositoryError("There are no commits yet") @@ -443,7 +458,9 @@ class GitRepository(BaseRepository): commit_id = "tip" if translate_tag: - commit_id = self._lookup_commit(commit_id, maybe_unreachable=maybe_unreachable) + commit_id = self._lookup_commit( + commit_id, maybe_unreachable=maybe_unreachable, + reference_obj=reference_obj) try: idx = self._commit_ids[commit_id] 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 @@ -437,7 +437,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): + translate_tag=None, maybe_unreachable=False, reference_obj=None): """ Returns ``MercurialCommit`` object representing repository's commit at the given `commit_id` or `commit_idx`. 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 @@ -277,7 +277,7 @@ class SubversionRepository(base.BaseRepo return os.path.join(self.path, 'hooks') def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, - translate_tag=None, maybe_unreachable=False): + translate_tag=None, maybe_unreachable=False, reference_obj=None): if self.is_empty(): raise EmptyRepositoryError("There are no commits yet") if commit_id is not None: 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 @@ -468,7 +468,7 @@ class FileNode(Node): mtype, encoding = db.guess_type(self.name) if mtype is None: - if self.is_binary: + if not self.is_largefile() and self.is_binary: mtype = 'application/octet-stream' encoding = None else: @@ -839,6 +839,7 @@ class LargeFileNode(FileNode): self.org_path = org_path self.kind = NodeKind.LARGEFILE self.alias = alias + self._content = '' def _validate_path(self, path): """ diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -2398,10 +2398,10 @@ class Repository(Base, BaseModel): # SCM PROPERTIES #========================================================================== - def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False): + def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None): return get_commit_safe( self.scm_instance(), commit_id, commit_idx, pre_load=pre_load, - maybe_unreachable=maybe_unreachable) + maybe_unreachable=maybe_unreachable, reference_obj=reference_obj) def get_changeset(self, rev=None, pre_load=None): warnings.warn("Use get_commit", DeprecationWarning) diff --git a/rhodecode/model/permission.py b/rhodecode/model/permission.py --- a/rhodecode/model/permission.py +++ b/rhodecode/model/permission.py @@ -41,6 +41,8 @@ class PermissionModel(BaseModel): """ Permissions model for RhodeCode """ + FORKING_DISABLED = 'hg.fork.none' + FORKING_ENABLED = 'hg.fork.repository' cls = Permission global_perms = { @@ -122,8 +124,8 @@ class PermissionModel(BaseModel): ('hg.repogroup.create.true', _('Enabled'))] c_obj.fork_choices = [ - ('hg.fork.none', _('Disabled')), - ('hg.fork.repository', _('Enabled'))] + (self.FORKING_DISABLED, _('Disabled')), + (self.FORKING_ENABLED, _('Enabled'))] c_obj.inherit_default_permission_choices = [ ('hg.inherit_default_perms.false', _('Disabled')), 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 @@ -908,7 +908,8 @@ class PullRequestModel(BaseModel): try: if source_ref_type in self.REF_TYPES: - source_commit = source_repo.get_commit(source_ref_name) + source_commit = source_repo.get_commit( + source_ref_name, reference_obj=pull_request.source_ref_parts) else: source_commit = source_repo.get_commit(source_ref_id) except CommitDoesNotExistError: @@ -922,7 +923,8 @@ class PullRequestModel(BaseModel): try: if target_ref_type in self.REF_TYPES: - target_commit = target_repo.get_commit(target_ref_name) + target_commit = target_repo.get_commit( + target_ref_name, reference_obj=pull_request.target_ref_parts) else: target_commit = target_repo.get_commit(target_ref_id) except CommitDoesNotExistError: diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -46,6 +46,7 @@ from rhodecode.model.db import ( Session, Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup, RepoGroup, RepositoryField, UserLog) +from rhodecode.model.permission import PermissionModel from rhodecode.model.settings import VcsSettingsModel log = logging.getLogger(__name__) @@ -422,8 +423,15 @@ class RepoModel(BaseModel): try: cur_repo = self._get_repo(repo) source_repo_name = cur_repo.repo_name + + affected_user_ids = [] if 'user' in kwargs: - cur_repo.user = User.get_by_username(kwargs['user']) + old_owner_id = cur_repo.user.user_id + new_owner = User.get_by_username(kwargs['user']) + cur_repo.user = new_owner + + if old_owner_id != new_owner.user_id: + affected_user_ids = [new_owner.user_id, old_owner_id] if 'repo_group' in kwargs: cur_repo.group = RepoGroup.get(kwargs['repo_group']) @@ -474,6 +482,9 @@ class RepoModel(BaseModel): self._rename_filesystem_repo( old=source_repo_name, new=new_name) + if affected_user_ids: + PermissionModel().trigger_permission_flush(affected_user_ids) + return cur_repo except Exception: log.error(traceback.format_exc()) 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 @@ -39,6 +39,7 @@ from rhodecode.model import BaseModel from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator, Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository) +from rhodecode.model.permission import PermissionModel from rhodecode.model.settings import VcsSettingsModel, SettingsModel from rhodecode.lib.caching_query import FromCache from rhodecode.lib.utils2 import action_logger_generic @@ -531,8 +532,14 @@ class RepoGroupModel(BaseModel): new_path = repo_group.full_path + affected_user_ids = [] if 'user' in form_data: - repo_group.user = User.get_by_username(form_data['user']) + old_owner_id = repo_group.user.user_id + new_owner = User.get_by_username(form_data['user']) + repo_group.user = new_owner + + if old_owner_id != new_owner.user_id: + affected_user_ids = [new_owner.user_id, old_owner_id] self.sa.add(repo_group) @@ -566,6 +573,9 @@ class RepoGroupModel(BaseModel): # Trigger update event. events.trigger(events.RepoGroupUpdateEvent(repo_group)) + if affected_user_ids: + PermissionModel().trigger_permission_flush(affected_user_ids) + return repo_group except Exception: log.error(traceback.format_exc()) 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 @@ -12,6 +12,12 @@ ******************************************************************************/ function registerRCRoutes() { // routes registration + pyroutes.register('admin_artifacts', '/_admin/artifacts', []); + pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []); + pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']); + pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []); + pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']); + pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']); pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']); pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []); pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []); diff --git a/rhodecode/public/js/src/rhodecode/comments.js b/rhodecode/public/js/src/rhodecode/comments.js --- a/rhodecode/public/js/src/rhodecode/comments.js +++ b/rhodecode/public/js/src/rhodecode/comments.js @@ -1331,7 +1331,7 @@ var CommentsController = function() { // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first if ($comments.length===0) { - var replBtn = ''.format(f_path, line_no) + var replBtn = ''.format(escapeHtml(f_path), line_no) var $reply_container = $('#cb-comments-inline-container-template') $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn); $td.append($($reply_container).html()); diff --git a/rhodecode/templates/base/base.mako b/rhodecode/templates/base/base.mako --- a/rhodecode/templates/base/base.mako +++ b/rhodecode/templates/base/base.mako @@ -115,6 +115,7 @@
  • ${_('Repository groups')}
  • ${_('Users')}
  • ${_('User groups')}
  • +
  • ${_('Artifacts')}
  • ${_('Permissions')}
  • ${_('Authentication')}
  • ${_('Integrations')}
  • diff --git a/rhodecode/templates/base/default_perms_box.mako b/rhodecode/templates/base/default_perms_box.mako --- a/rhodecode/templates/base/default_perms_box.mako +++ b/rhodecode/templates/base/default_perms_box.mako @@ -62,7 +62,7 @@
    ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)} ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)} - ${_('Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups.')} + ${_('Permission to create repository forks. Root level forks will only work if repository creation is enabled.')}
    diff --git a/rhodecode/templates/compare/compare_commits.mako b/rhodecode/templates/compare/compare_commits.mako --- a/rhodecode/templates/compare/compare_commits.mako +++ b/rhodecode/templates/compare/compare_commits.mako @@ -24,7 +24,7 @@ ## to speed up lookups cache some functions before the loop <% active_patterns = h.get_active_pattern_entries(c.repo_name) - urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns, issues_container=getattr(c, 'referenced_commit_issues', None)) + urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns) %> %for commit in c.commit_ranges: @@ -57,7 +57,7 @@
    -
    ${urlify_commit_message(commit.message, c.repo_name)}
    +
    ${urlify_commit_message(commit.message, c.repo_name, issues_container_callback=getattr(c, 'referenced_commit_issues', h.IssuesRegistry())(commit.serialize()))}
    diff --git a/rhodecode/templates/data_table/_dt_elements.mako b/rhodecode/templates/data_table/_dt_elements.mako --- a/rhodecode/templates/data_table/_dt_elements.mako +++ b/rhodecode/templates/data_table/_dt_elements.mako @@ -416,6 +416,12 @@ +<%def name="repo_artifact_admin_name(file_uid, artifact_display_name)"> + + ${(artifact_display_name or '_EMPTY_NAME_')} + + + <%def name="repo_artifact_uid(repo_name, file_uid)"> ${h.shorter(file_uid, size=24, prefix=True)} @@ -443,6 +449,7 @@ % endif + <%def name="markup_form(form_id, form_text='', help_text=None)">
    diff --git a/rhodecode/templates/ejs_templates/templates.html b/rhodecode/templates/ejs_templates/templates.html --- a/rhodecode/templates/ejs_templates/templates.html +++ b/rhodecode/templates/ejs_templates/templates.html @@ -221,7 +221,7 @@ if (show_disabled) { <%= version_info %> <% } %>
    - File: <%- file_name -%> + File: <%= file_name -%> <% } else { %> <% if (review_status) { %> 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 @@ -27,8 +27,8 @@ <%def name="main()"> ## Container to gather extracted Tickets <% - c.referenced_commit_issues = [] - c.referenced_desc_issues = [] + c.referenced_commit_issues = h.IssuesRegistry() + c.referenced_desc_issues = h.IssuesRegistry() %>