##// END OF EJS Templates
release: Merge default into stable for release preparation
milka -
r4671:d6153155 merge stable
parent child Browse files
Show More
@@ -0,0 +1,76 b''
1 |RCE| 4.25.0 |RNS|
2 ------------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2021-04-02
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13 - SSH: allow clone by ID via SSH operations.
14 - Artifacts: added an admin panel to manage artifacts.
15 - Redmine: added option to add note to a ticket without changing its status in Redmine integration.
16
17
18 General
19 ^^^^^^^
20
21 - Git: change lookups logic. Prioritize reference names over numerical ids.
22 Numerical ids are supported as a fallback if ref matching is unsuccessful.
23 - Permissions: changed fork permission help text to reflect the actual state on how it works.
24 - Permissions: flush permissions on owner changes for repo and repo groups. This
25 would fix problems when owner of repository changes then the new owner lacked permissions
26 until cache expired.
27 - Artifacts: added API function to remove artifacts.
28 - Archives: use a special name for non-hashed archives to fix caching issues.
29 - Packaging: fixed few packages requirements for a proper builds.
30 - Packaging: fix rhodecode-tools for docker builds.
31 - Packaging: fixed some problem after latest setuptools-scm release.
32 - Packaging: added setuptools-scm to packages for build.
33 - Packaging: fix jira package for reproducible builds.
34 - Packaging: fix zipp package patches.
35
36
37 Security
38 ^^^^^^^^
39
40 - Comments: forbid removal of comments by anyone except the owners.
41 Previously admins of a repository could remove them if they would construct a special url with data.
42 - Pull requests: fixed some xss problems when a deleted file with special characters were commented on.
43
44
45 Performance
46 ^^^^^^^^^^^
47
48 - License: skip channelstream connect on license checks logic to reduce calls handling times.
49 - Core: optimize some calls to skip license/scm detection on them. Each license check is expensive
50 and we don't need them on each call.
51
52
53 Fixes
54 ^^^^^
55
56 - Branch-permissions: fixed ce view. Fixes #5656
57 - Feed: fix errors on feed access of empty repositories.
58 - Archives: if implicit ref name was used (e.g master) to obtain archive, we now
59 redirect to explicit commit sha so we can have the proper caching for references names.
60 - rcextensions: fixed pre-files extractor return code support.
61 - Svn: fix subprocess problems on some of the calls for file checking.
62 - Pull requests: fixed multiple repetitions of referenced tickets in pull requests summary sidebar.
63 - Maintenance: fixed bad routes def
64 - clone-uri: fixed the problems with key mismatch that caused errors on summary page.
65 - Largefiles: added fix for downloading largefiles which had no extension in file name.
66 - Compare: fix referenced commits bug.
67 - Git: fix for unicode branches
68
69
70 Upgrade notes
71 ^^^^^^^^^^^^^
72
73 - Scheduled release 4.25.0.
74
75
76
@@ -0,0 +1,13 b''
1 diff -rup channelstream-0.6.14-orig/setup.py channelstream-0.6.14/setup.py
2
3 --- channelstream-0.6.14/setup-orig.py 2021-03-11 12:34:45.000000000 +0100
4 +++ channelstream-0.6.14/setup.py 2021-03-11 12:34:56.000000000 +0100
5 @@ -52,7 +52,7 @@ setup(
6 include_package_data=True,
7 install_requires=requires,
8 python_requires=">=2.7",
9 - setup_requires=["pytest-runner"],
10 + setup_requires=["pytest-runner==5.1.0"],
11 extras_require={
12 "dev": ["coverage", "pytest", "pyramid", "tox", "mock", "webtest"],
13 "lint": ["black"],
@@ -0,0 +1,10 b''
1 diff -rup configparser-4.0.2-orig/pyproject.toml configparser-4.0.2/pyproject.toml
2 --- configparser-4.0.2-orig/pyproject.toml 2021-03-22 21:28:11.000000000 +0100
3 +++ configparser-4.0.2/pyproject.toml 2021-03-22 21:28:11.000000000 +0100
4 @@ -1,5 +1,5 @@
5 [build-system]
6 -requires = ["setuptools>=40.7", "wheel", "setuptools_scm>=1.15"]
7 +requires = ["setuptools<=42.0", "wheel", "setuptools_scm<6.0.0"]
8 build-backend = "setuptools.build_meta"
9
10 [tool.black]
@@ -0,0 +1,7 b''
1 diff -rup importlib-metadata-1.6.0-orig/yproject.toml importlib-metadata-1.6.0/pyproject.toml
2 --- importlib-metadata-1.6.0-orig/yproject.toml 2021-03-22 22:10:33.000000000 +0100
3 +++ importlib-metadata-1.6.0/pyproject.toml 2021-03-22 22:11:09.000000000 +0100
4 @@ -1,3 +1,3 @@
5 [build-system]
6 -requires = ["setuptools>=30.3", "wheel", "setuptools_scm"]
7 +requires = ["setuptools<42.0", "wheel", "setuptools_scm<6.0.0"]
@@ -0,0 +1,12 b''
1 diff -rup pyramid-apispec-0.3.2-orig/setup.py pyramid-apispec-0.3.2/setup.py
2 --- pyramid-apispec-0.3.2-orig/setup.py 2021-03-11 11:19:26.000000000 +0100
3 +++ pyramid-apispec-0.3.2/setup.py 2021-03-11 11:19:51.000000000 +0100
4 @@ -44,7 +44,7 @@ setup(
5 packages=find_packages(exclude=["contrib", "docs", "tests"]),
6 package_data={"pyramid_apispec": ["static/*.*"], "": ["LICENSE"]},
7 install_requires=["apispec[yaml]==1.0.0"],
8 - setup_requires=["pytest-runner"],
9 + setup_requires=["pytest-runner==5.1"],
10 extras_require={
11 "dev": ["coverage", "pytest", "pyramid", "tox", "webtest"],
12 "demo": ["marshmallow==2.15.3", "pyramid", "apispec", "webtest"], No newline at end of file
@@ -0,0 +1,12 b''
1 diff -rup rhodecode-tools-1.4.0-orig/setup.py rhodecode-tools-1.4.0/setup.py
2 --- rhodecode-tools-1.4.0/setup-orig.py 2021-03-11 12:34:45.000000000 +0100
3 +++ rhodecode-tools-1.4.0/setup.py 2021-03-11 12:34:56.000000000 +0100
4 @@ -69,7 +69,7 @@ def _get_requirements(req_filename, excl
5
6
7 # requirements extract
8 -setup_requirements = ['pytest-runner']
9 +setup_requirements = ['pytest-runner==5.1.0']
10 install_requirements = _get_requirements(
11 'requirements.txt', exclude=['setuptools'])
12 test_requirements = _get_requirements('requirements_test.txt') No newline at end of file
@@ -0,0 +1,10 b''
1 diff -rup zip-1.2.0-orig/pyproject.toml zip-1.2.0/pyproject.toml
2 --- zip-1.2.0-orig/pyproject.toml 2021-03-23 10:55:37.000000000 +0100
3 +++ zip-1.2.0/pyproject.toml 2021-03-23 10:56:05.000000000 +0100
4 @@ -1,5 +1,5 @@
5 [build-system]
6 -requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"]
7 +requires = ["setuptools<42.0", "wheel", "setuptools_scm<6.0.0"]
8 build-backend = "setuptools.build_meta"
9
10 [tool.black]
@@ -0,0 +1,74 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import mock
22 import pytest
23
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
26 from rhodecode.model.db import Repository, UserRepoToPerm, Permission, User
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import (
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, assert_session_flash)
30 from rhodecode.tests.fixture import Fixture
31
32 fixture = Fixture()
33
34
35 def route_path(name, params=None, **kwargs):
36 import urllib
37
38 base_url = {
39 'edit_repo_maintenance': '/{repo_name}/settings/maintenance',
40 'edit_repo_maintenance_execute': '/{repo_name}/settings/maintenance/execute',
41
42 }[name].format(**kwargs)
43
44 if params:
45 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
46 return base_url
47
48
49 def _get_permission_for_user(user, repo):
50 perm = UserRepoToPerm.query()\
51 .filter(UserRepoToPerm.repository ==
52 Repository.get_by_repo_name(repo))\
53 .filter(UserRepoToPerm.user == User.get_by_username(user))\
54 .all()
55 return perm
56
57
58 @pytest.mark.usefixtures('autologin_user', 'app')
59 class TestAdminRepoMaintenance(object):
60 @pytest.mark.parametrize('urlname', [
61 'edit_repo_maintenance',
62 ])
63 def test_show_page(self, urlname, app, backend):
64 app.get(route_path(urlname, repo_name=backend.repo_name), status=200)
65
66 def test_execute_maintenance_for_repo_hg(self, app, backend_hg, autologin_user, xhr_header):
67 repo_name = backend_hg.repo_name
68
69 response = app.get(
70 route_path('edit_repo_maintenance_execute',
71 repo_name=repo_name,),
72 extra_environ=xhr_header)
73
74 assert "HG Verify repo" in ''.join(response.json)
@@ -1,6 +1,5 b''
1 1 [bumpversion]
2 current_version = 4.24.1
2 current_version = 4.25.0
3 3 message = release: Bump version {current_version} to {new_version}
4 4
5 5 [bumpversion:file:rhodecode/VERSION]
6
@@ -5,25 +5,20 b' done = false'
5 5 done = true
6 6
7 7 [task:rc_tools_pinned]
8 done = true
9 8
10 9 [task:fixes_on_stable]
11 done = true
12 10
13 11 [task:pip2nix_generated]
14 done = true
15 12
16 13 [task:changelog_updated]
17 done = true
18 14
19 15 [task:generate_api_docs]
20 done = true
16
17 [task:updated_translation]
21 18
22 19 [release]
23 state = prepared
24 version = 4.24.1
25
26 [task:updated_translation]
20 state = in_progress
21 version = 4.25.0
27 22
28 23 [task:generate_js_routes]
29 24
@@ -130,6 +130,24 b' file_store_get_info (EE only)'
130 130 error : null
131 131
132 132
133 file_store_delete (EE only)
134 ---------------------------
135
136 .. py:function:: file_store_delete(apiuser, store_fid)
137
138 Delete an artifact based on the secret uuid.
139
140 Example output:
141
142 .. code-block:: bash
143
144 id : <id_given_in_input>
145 result: {
146 "artifact" : {"uid": "some uid", "removed": true}
147 }
148 error : null
149
150
133 151 file_store_add_metadata (EE only)
134 152 ---------------------------------
135 153
@@ -9,6 +9,7 b' Release Notes'
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.25.0.rst
12 13 release-notes-4.24.1.rst
13 14 release-notes-4.24.0.rst
14 15 release-notes-4.23.2.rst
@@ -6,7 +6,7 b' diff -rup pytest-4.6.5-orig/setup.py pyt'
6 6 setup(
7 7 use_scm_version={"write_to": "src/_pytest/_version.py"},
8 8 - setup_requires=["setuptools-scm", "setuptools>=40.0"],
9 + setup_requires=["setuptools-scm", "setuptools<=42.0"],
9 + setup_requires=["setuptools-scm<6.0.0", "setuptools<=42.0"],
10 10 package_dir={"": "src"},
11 11 # fmt: off
12 12 extras_require={ No newline at end of file
@@ -280,6 +280,72 b' self: super: {'
280 280 ];
281 281 });
282 282
283 "pytest-runner" = super."pytest-runner".override (attrs: {
284 propagatedBuildInputs = [
285 self."setuptools-scm"
286 ];
287 });
288
289 "py" = super."py".override (attrs: {
290 propagatedBuildInputs = [
291 self."setuptools-scm"
292 ];
293 });
294
295 "python-dateutil" = super."python-dateutil".override (attrs: {
296 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
297 self."setuptools-scm"
298 ];
299 });
300
301 "configparser" = super."configparser".override (attrs: {
302 patches = [
303 ./patches/configparser/pyproject.patch
304 ];
305 propagatedBuildInputs = [
306 self."setuptools-scm"
307 ];
308 });
309
310 "importlib-metadata" = super."importlib-metadata".override (attrs: {
311
312 patches = [
313 ./patches/importlib_metadata/pyproject.patch
314 ];
315
316 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
317 self."setuptools-scm"
318 ];
319
320 });
321
322 "zipp" = super."zipp".override (attrs: {
323 patches = [
324 ./patches/zipp/pyproject.patch
325 ];
326 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
327 self."setuptools-scm"
328 ];
329 });
330
331 "pyramid-apispec" = super."pyramid-apispec".override (attrs: {
332 patches = [
333 ./patches/pyramid_apispec/setuptools.patch
334 ];
335 });
336
337 "channelstream" = super."channelstream".override (attrs: {
338 patches = [
339 ./patches/channelstream/setuptools.patch
340 ];
341 });
342
343 "rhodecode-tools" = super."rhodecode-tools".override (attrs: {
344 patches = [
345 ./patches/rhodecode_tools/setuptools.patch
346 ];
347 });
348
283 349 # Avoid that base packages screw up the build process
284 350 inherit (basePythonPackages)
285 351 setuptools;
@@ -1883,7 +1883,7 b' self: super: {'
1883 1883 };
1884 1884 };
1885 1885 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1886 name = "rhodecode-enterprise-ce-4.24.1";
1886 name = "rhodecode-enterprise-ce-4.25.0";
1887 1887 buildInputs = [
1888 1888 self."pytest"
1889 1889 self."py"
@@ -2092,6 +2092,17 b' self: super: {'
2092 2092 license = [ pkgs.lib.licenses.mit ];
2093 2093 };
2094 2094 };
2095 "setuptools-scm" = super.buildPythonPackage {
2096 name = "setuptools-scm-3.5.0";
2097 doCheck = false;
2098 src = fetchurl {
2099 url = "https://files.pythonhosted.org/packages/b2/f7/60a645aae001a2e06cf4b8db2fba9d9f36b8fd378f10647e3e218b61b74b/setuptools_scm-3.5.0.tar.gz";
2100 sha256 = "5bdf21a05792903cafe7ae0c9501182ab52497614fa6b1750d9dbae7b60c1a87";
2101 };
2102 meta = {
2103 license = [ pkgs.lib.licenses.psfl ];
2104 };
2105 };
2095 2106 "simplegeneric" = super.buildPythonPackage {
2096 2107 name = "simplegeneric-0.8.1";
2097 2108 doCheck = false;
@@ -1,1 +1,1 b''
1 4.24.1 No newline at end of file
1 4.25.0 No newline at end of file
@@ -1212,7 +1212,7 b' def fork_repo(request, apiuser, repoid, '
1212 1212 validate_repo_permissions(apiuser, repoid, repo, _perms)
1213 1213
1214 1214 # check if the regular user has at least fork permissions as well
1215 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1215 if not HasPermissionAnyApi(PermissionModel.FORKING_ENABLED)(user=apiuser):
1216 1216 raise JSONRPCForbidden()
1217 1217
1218 1218 # check if user can set owner parameter
@@ -255,7 +255,7 b' class LocalFileStorage(object):'
255 255
256 256 return filename, metadata
257 257
258 def get_metadata(self, filename):
258 def get_metadata(self, filename, ignore_missing=False):
259 259 """
260 260 Reads JSON stored metadata for a file
261 261
@@ -264,6 +264,7 b' class LocalFileStorage(object):'
264 264 """
265 265 filename = self.store_path(filename)
266 266 filename_meta = filename + '.meta'
267
267 if ignore_missing and not os.path.isfile(filename_meta):
268 return {}
268 269 with open(filename_meta, "rb") as source_meta:
269 270 return json.loads(source_meta.read())
@@ -932,8 +932,8 b' def includeme(config):'
932 932 name='edit_repo_perms_branch',
933 933 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
934 934 config.add_view(
935 RepoBranchesView,
936 attr='branches',
935 RepoSettingsBranchPermissionsView,
936 attr='branch_permissions',
937 937 route_name='edit_repo_perms_branch', request_method='GET',
938 938 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
939 939
@@ -950,8 +950,8 b' def includeme(config):'
950 950 config.add_view(
951 951 RepoMaintenanceView,
952 952 attr='repo_maintenance',
953 route_name='edit_repo_maintenance_execute', request_method='GET',
954 renderer='json', xhr=True)
953 route_name='edit_repo_maintenance', request_method='GET',
954 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
955 955
956 956 config.add_route(
957 957 name='edit_repo_maintenance_execute',
@@ -959,8 +959,8 b' def includeme(config):'
959 959 config.add_view(
960 960 RepoMaintenanceView,
961 961 attr='repo_maintenance_execute',
962 route_name='edit_repo_maintenance', request_method='GET',
963 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
962 route_name='edit_repo_maintenance_execute', request_method='GET',
963 renderer='json', xhr=True)
964 964
965 965 # Fields
966 966 config.add_route(
@@ -542,6 +542,28 b' class TestRepositoryArchival(object):'
542 542 for header in headers:
543 543 assert header in response.headers.items()
544 544
545 def test_archival_no_hash(self, backend):
546 backend.enable_downloads()
547 commit = backend.repo.get_commit(commit_idx=173)
548 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
549
550 short = 'plain' + extension
551 fname = commit.raw_id + extension
552 filename = '%s-%s' % (backend.repo_name, short)
553 response = self.app.get(
554 route_path('repo_archivefile',
555 repo_name=backend.repo_name,
556 fname=fname, params={'with_hash': 0}))
557
558 assert response.status == '200 OK'
559 headers = [
560 ('Content-Disposition', 'attachment; filename=%s' % filename),
561 ('Content-Type', '%s' % content_type),
562 ]
563
564 for header in headers:
565 assert header in response.headers.items()
566
545 567 @pytest.mark.parametrize('arch_ext',[
546 568 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
547 569 def test_archival_wrong_ext(self, backend, arch_ext):
@@ -34,7 +34,7 b' from rhodecode.lib.auth import ('
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool, safe_unicode
38 38 from rhodecode.lib.vcs.exceptions import (
39 39 RepositoryError, CommitDoesNotExistError,
40 40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
@@ -110,7 +110,7 b' class RepoChangelogView(RepoAppView):'
110 110
111 111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
113 h.flash(u'Branch {} is not found.'.format(h.escape(safe_unicode(branch_name))),
114 114 category='warning')
115 115 redirect_url = h.route_path(
116 116 'repo_commits_file', repo_name=repo_name,
@@ -674,6 +674,10 b' class RepoCommitsView(RepoAppView):'
674 674 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
675 675 comment_repo_admin = is_repo_admin and is_repo_comment
676 676
677 if comment.draft and not comment_owner:
678 # We never allow to delete draft comments for other than owners
679 raise HTTPNotFound()
680
677 681 if super_admin or comment_owner or comment_repo_admin:
678 682 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
679 683 Session().commit()
@@ -104,6 +104,9 b' class RepoFeedView(RepoAppView):'
104 104
105 105 def _get_commits(self):
106 106 pre_load = ['author', 'branch', 'date', 'message', 'parents']
107 if self.rhodecode_vcs_repo.is_empty():
108 return []
109
107 110 collection = self.rhodecode_vcs_repo.get_commits(
108 111 branch_name=None, show_hidden=False, pre_load=pre_load,
109 112 translate_tags=False)
@@ -137,6 +140,7 b' class RepoFeedView(RepoAppView):'
137 140 language=self.language,
138 141 ttl=self.ttl
139 142 )
143
140 144 for commit in reversed(self._get_commits()):
141 145 date = self._set_timezone(commit.date)
142 146 feed.add_item(
@@ -325,17 +325,18 b' class RepoFilesView(RepoAppView):'
325 325
326 326 return lf_enabled
327 327
328 def _get_archive_name(self, db_repo_name, commit_sha, ext, subrepos=False, path_sha=''):
328 def _get_archive_name(self, db_repo_name, commit_sha, ext, subrepos=False, path_sha='', with_hash=True):
329 329 # original backward compat name of archive
330 330 clean_name = safe_str(db_repo_name.replace('/', '_'))
331 331
332 332 # e.g vcsserver.zip
333 333 # e.g vcsserver-abcdefgh.zip
334 334 # e.g vcsserver-abcdefgh-defghijk.zip
335 archive_name = '{}{}{}{}{}'.format(
335 archive_name = '{}{}{}{}{}{}'.format(
336 336 clean_name,
337 337 '-sub' if subrepos else '',
338 338 commit_sha,
339 '-{}'.format('plain') if not with_hash else '',
339 340 '-{}'.format(path_sha) if path_sha else '',
340 341 ext)
341 342 return archive_name
@@ -372,6 +373,11 b' class RepoFilesView(RepoAppView):'
372 373 except EmptyRepositoryError:
373 374 return Response(_('Empty repository'))
374 375
376 # we used a ref, or a shorter version, lets redirect client ot use explicit hash
377 if commit_id != commit.raw_id:
378 fname='{}{}'.format(commit.raw_id, ext)
379 raise HTTPFound(self.request.current_route_path(fname=fname))
380
375 381 try:
376 382 at_path = commit.get_node(at_path).path or default_at_path
377 383 except Exception:
@@ -385,7 +391,7 b' class RepoFilesView(RepoAppView):'
385 391 # used for cache etc
386 392 archive_name = self._get_archive_name(
387 393 self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos,
388 path_sha=path_sha)
394 path_sha=path_sha, with_hash=with_hash)
389 395
390 396 if not with_hash:
391 397 short_sha = ''
@@ -394,7 +400,7 b' class RepoFilesView(RepoAppView):'
394 400 # what end client gets served
395 401 response_archive_name = self._get_archive_name(
396 402 self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos,
397 path_sha=path_sha)
403 path_sha=path_sha, with_hash=with_hash)
398 404 # remove extension from our archive directory name
399 405 archive_dir_name = response_archive_name[:-len(ext)]
400 406
@@ -404,9 +410,10 b' class RepoFilesView(RepoAppView):'
404 410 cached_archive_path = None
405 411
406 412 if archive_cache_enabled:
407 # check if we it's ok to write
413 # check if we it's ok to write, and re-create the archive cache
408 414 if not os.path.isdir(CONFIG['archive_cache_dir']):
409 415 os.makedirs(CONFIG['archive_cache_dir'])
416
410 417 cached_archive_path = os.path.join(
411 418 CONFIG['archive_cache_dir'], archive_name)
412 419 if os.path.isfile(cached_archive_path):
@@ -165,7 +165,7 b' class RepoForksView(RepoAppView, DataGri'
165 165
166 166 @LoginRequired()
167 167 @NotAnonymous()
168 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
168 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
169 169 @HasRepoPermissionAnyDecorator(
170 170 'repository.read', 'repository.write', 'repository.admin')
171 171 def repo_fork_new(self):
@@ -191,7 +191,7 b' class RepoForksView(RepoAppView, DataGri'
191 191
192 192 @LoginRequired()
193 193 @NotAnonymous()
194 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
194 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
195 195 @HasRepoPermissionAnyDecorator(
196 196 'repository.read', 'repository.write', 'repository.admin')
197 197 @CSRFRequired()
@@ -1748,6 +1748,10 b' class RepoPullRequestsView(RepoAppView, '
1748 1748 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1749 1749 comment_repo_admin = is_repo_admin and is_repo_comment
1750 1750
1751 if comment.draft and not comment_owner:
1752 # We never allow to delete draft comments for other than owners
1753 raise HTTPNotFound()
1754
1751 1755 if super_admin or comment_owner or comment_repo_admin:
1752 1756 old_calculated_status = comment.pull_request.calculated_review_status()
1753 1757 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
@@ -89,6 +89,14 b' class SshWrapper(object):'
89 89
90 90 return conn
91 91
92 def maybe_translate_repo_uid(self, repo_name):
93 if repo_name.startswith('_'):
94 from rhodecode.model.repo import RepoModel
95 by_id_match = RepoModel().get_repo_by_id(repo_name)
96 if by_id_match:
97 repo_name = by_id_match.repo_name
98 return repo_name
99
92 100 def get_repo_details(self, mode):
93 101 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
94 102 repo_name = None
@@ -97,14 +105,14 b' class SshWrapper(object):'
97 105 hg_match = re.match(hg_pattern, self.command)
98 106 if hg_match is not None:
99 107 vcs_type = 'hg'
100 repo_name = hg_match.group(1).strip('/')
108 repo_name = self.maybe_translate_repo_uid(hg_match.group(1).strip('/'))
101 109 return vcs_type, repo_name, mode
102 110
103 111 git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$'
104 112 git_match = re.match(git_pattern, self.command)
105 113 if git_match is not None:
106 114 vcs_type = 'git'
107 repo_name = git_match.group(2).strip('/')
115 repo_name = self.maybe_translate_repo_uid(git_match.group(2).strip('/'))
108 116 mode = git_match.group(1)
109 117 return vcs_type, repo_name, mode
110 118
@@ -19,6 +19,8 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 import os
23
22 24 import mock
23 25 import pytest
24 26
@@ -107,6 +109,8 b' class TestGitServer(object):'
107 109 def test_run_returns_executes_command(self, git_server):
108 110 server = git_server.create()
109 111 from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper
112
113 os.environ['SSH_CLIENT'] = '127.0.0.1'
110 114 with mock.patch.object(GitTunnelWrapper, 'create_hooks_env') as _patch:
111 115 _patch.return_value = 0
112 116 with mock.patch.object(GitTunnelWrapper, 'command', return_value='date'):
@@ -108,6 +108,7 b' class TestMercurialServer(object):'
108 108 def test_run_returns_executes_command(self, hg_server):
109 109 server = hg_server.create()
110 110 from rhodecode.apps.ssh_support.lib.backends.hg import MercurialTunnelWrapper
111 os.environ['SSH_CLIENT'] = '127.0.0.1'
111 112 with mock.patch.object(MercurialTunnelWrapper, 'create_hooks_env') as _patch:
112 113 _patch.return_value = 0
113 114 with mock.patch.object(MercurialTunnelWrapper, 'command', return_value='date'):
@@ -17,7 +17,7 b''
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 import os
21 21 import mock
22 22 import pytest
23 23
@@ -174,6 +174,7 b' class TestSubversionServer(object):'
174 174 def test_run_returns_executes_command(self, svn_server):
175 175 server = svn_server.create()
176 176 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
177 os.environ['SSH_CLIENT'] = '127.0.0.1'
177 178 with mock.patch.object(
178 179 SubversionTunnelWrapper, 'get_first_client_response',
179 180 return_value={'url': 'http://server/test-svn'}):
@@ -32,6 +32,7 b' import json'
32 32 from rhodecode.lib import diffs
33 33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 34 from rhodecode.lib.vcs.backends.git.diff import GitDiff
35 from vcsserver.utils import safe_int
35 36
36 37
37 38 def get_svn_files(repo, vcs_repo, refs):
@@ -74,7 +75,7 b' def get_svn_files(repo, vcs_repo, refs):'
74 75 # skip dirs
75 76 continue
76 77
77 parsed_entry['file_size'] = int(stdout.strip())
78 parsed_entry['file_size'] = safe_int(stdout.strip()) or 0
78 79
79 80 files.append(parsed_entry)
80 81
@@ -524,8 +524,10 b' class PermissionCalculator(object):'
524 524
525 525 # In case we want to extend this list we should make sure
526 526 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
527 from rhodecode.model.permission import PermissionModel
528
527 529 _configurable = frozenset([
528 'hg.fork.none', 'hg.fork.repository',
530 PermissionModel.FORKING_DISABLED, PermissionModel.FORKING_ENABLED,
529 531 'hg.create.none', 'hg.create.repository',
530 532 'hg.usergroup.create.false', 'hg.usergroup.create.true',
531 533 'hg.repogroup.create.false', 'hg.repogroup.create.true',
@@ -25,6 +25,7 b' Consists of functions to typically be us'
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27 import base64
28 import collections
28 29
29 30 import os
30 31 import random
@@ -1733,7 +1734,7 b' def process_patterns(text_string, repo_n'
1733 1734
1734 1735
1735 1736 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None,
1736 issues_container=None, error_container=None):
1737 issues_container_callback=None, error_container=None):
1737 1738 """
1738 1739 Parses given text message and makes proper links.
1739 1740 issues are linked to given issue-server, and rest is a commit link
@@ -1756,8 +1757,9 b' def urlify_commit_message(commit_text, r'
1756 1757 new_text, issues, errors = process_patterns(
1757 1758 new_text, repository or '', active_entries=active_pattern_entries)
1758 1759
1759 if issues_container is not None:
1760 issues_container.extend(issues)
1760 if issues_container_callback is not None:
1761 for issue in issues:
1762 issues_container_callback(issue)
1761 1763
1762 1764 if error_container is not None:
1763 1765 error_container.extend(errors)
@@ -1802,7 +1804,7 b' def renderer_from_filename(filename, exc'
1802 1804
1803 1805
1804 1806 def render(source, renderer='rst', mentions=False, relative_urls=None,
1805 repo_name=None, active_pattern_entries=None, issues_container=None):
1807 repo_name=None, active_pattern_entries=None, issues_container_callback=None):
1806 1808
1807 1809 def maybe_convert_relative_links(html_source):
1808 1810 if relative_urls:
@@ -1819,8 +1821,9 b" def render(source, renderer='rst', menti"
1819 1821 source, issues, errors = process_patterns(
1820 1822 source, repo_name, link_format='rst',
1821 1823 active_entries=active_pattern_entries)
1822 if issues_container is not None:
1823 issues_container.extend(issues)
1824 if issues_container_callback is not None:
1825 for issue in issues:
1826 issues_container_callback(issue)
1824 1827
1825 1828 return literal(
1826 1829 '<div class="rst-block">%s</div>' %
@@ -1833,8 +1836,10 b" def render(source, renderer='rst', menti"
1833 1836 source, issues, errors = process_patterns(
1834 1837 source, repo_name, link_format='markdown',
1835 1838 active_entries=active_pattern_entries)
1836 if issues_container is not None:
1837 issues_container.extend(issues)
1839 if issues_container_callback is not None:
1840 for issue in issues:
1841 issues_container_callback(issue)
1842
1838 1843
1839 1844 return literal(
1840 1845 '<div class="markdown-block">%s</div>' %
@@ -2115,3 +2120,29 b' def is_active(menu_entry, selected):'
2115 2120
2116 2121 if selected in menu_entry:
2117 2122 return "active"
2123
2124
2125 class IssuesRegistry(object):
2126 """
2127 issue_registry = IssuesRegistry()
2128 some_func(issues_callback=issues_registry(...))
2129 """
2130
2131 def __init__(self):
2132 self.issues = []
2133 self.unique_issues = collections.defaultdict(lambda: [])
2134
2135 def __call__(self, commit_dict=None):
2136 def callback(issue):
2137 if commit_dict and issue:
2138 issue['commit'] = commit_dict
2139 self.issues.append(issue)
2140 self.unique_issues[issue['id']].append(issue)
2141 return callback
2142
2143 def get_issues(self):
2144 return self.issues
2145
2146 @property
2147 def issues_unique_count(self):
2148 return len(set(i['id'] for i in self.issues))
@@ -161,15 +161,28 b' def detect_vcs_request(environ, backends'
161 161 # List of path views first chunk we don't do any checks
162 162 white_list = [
163 163 # e.g /_file_store/download
164 '_file_store'
164 '_file_store',
165
166 # static files no detection
167 '_static',
168
169 # full channelstream connect should be VCS skipped
170 '_admin/channelstream/connect',
165 171 ]
166 172
167 173 path_info = environ['PATH_INFO']
168 174
169 if get_path_elem(path_info) in white_list:
175 path_elem = get_path_elem(path_info)
176
177 if path_elem in white_list:
170 178 log.debug('path `%s` in whitelist, skipping...', path_info)
171 179 return handler
172 180
181 path_url = path_info.lstrip('/')
182 if path_url in white_list:
183 log.debug('full url path `%s` in whitelist, skipping...', path_url)
184 return handler
185
173 186 if VCS_TYPE_KEY in environ:
174 187 raw_type = environ[VCS_TYPE_KEY]
175 188 if raw_type == VCS_TYPE_SKIP:
@@ -181,7 +194,7 b' def detect_vcs_request(environ, backends'
181 194 log.debug('got handler:%s from environ', handler)
182 195
183 196 if not handler:
184 log.debug('request start: checking if request is of VCS type in order: %s', backends)
197 log.debug('request start: checking if request for `%s` is of VCS type in order: %s', path_elem, backends)
185 198 for vcs_type in backends:
186 199 vcs_check, _handler = checks[vcs_type]
187 200 if vcs_check(environ):
@@ -594,6 +594,9 b' def credentials_filter(uri):'
594 594 :param uri:
595 595 """
596 596 import urlobject
597 if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue):
598 return 'InvalidDecryptionKey'
599
597 600 url_obj = urlobject.URLObject(cleaned_uri(uri))
598 601 url_obj = url_obj.without_password().without_username()
599 602
@@ -655,7 +658,7 b' def get_clone_url(request, uri_tmpl, rep'
655 658
656 659
657 660 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
658 maybe_unreachable=False):
661 maybe_unreachable=False, reference_obj=None):
659 662 """
660 663 Safe version of get_commit if this commit doesn't exists for a
661 664 repository it returns a Dummy one instead
@@ -665,6 +668,7 b' def get_commit_safe(repo, commit_id=None'
665 668 :param commit_idx: numeric commit index
666 669 :param pre_load: optional list of commit attributes to load
667 670 :param maybe_unreachable: translate unreachable commits on git repos
671 :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123"
668 672 """
669 673 # TODO(skreft): remove these circular imports
670 674 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
@@ -676,7 +680,7 b' def get_commit_safe(repo, commit_id=None'
676 680 try:
677 681 commit = repo.get_commit(
678 682 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
679 maybe_unreachable=maybe_unreachable)
683 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
680 684 except (RepositoryError, LookupError):
681 685 commit = EmptyCommit()
682 686 return commit
@@ -72,6 +72,10 b' class Reference(_Reference):'
72 72 if self.type == 'book':
73 73 return self.name
74 74
75 @property
76 def to_unicode(self):
77 return reference_to_unicode(self)
78
75 79
76 80 def unicode_to_reference(raw):
77 81 """
@@ -483,7 +487,7 b' class BaseRepository(object):'
483 487 self._is_empty = False
484 488
485 489 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
486 translate_tag=None, maybe_unreachable=False):
490 translate_tag=None, maybe_unreachable=False, reference_obj=None):
487 491 """
488 492 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
489 493 are both None, most recent commit is returned.
@@ -98,7 +98,7 b' class GitCommit(base.BaseCommit):'
98 98 elif attr == "parents":
99 99 value = self._make_commits(value)
100 100 elif attr == "branch":
101 value = value[0] if value else None
101 value = self._set_branch(value)
102 102 self.__dict__[attr] = value
103 103
104 104 @LazyProperty
@@ -156,13 +156,15 b' class GitCommit(base.BaseCommit):'
156 156 branches.append(name)
157 157 return branches
158 158
159 def _set_branch(self, branches):
160 if branches:
161 # actually commit can have multiple branches in git
162 return safe_unicode(branches[0])
163
159 164 @LazyProperty
160 165 def branch(self):
161 166 branches = self._remote.branch(self.raw_id)
162
163 if branches:
164 # actually commit can have multiple branches in git
165 return safe_unicode(branches[0])
167 return self._set_branch(branches)
166 168
167 169 def _get_tree_id_for_path(self, path):
168 170 path = safe_str(path)
@@ -228,7 +228,8 b' class GitRepository(BaseRepository):'
228 228 return []
229 229 return output.splitlines()
230 230
231 def _lookup_commit(self, commit_id_or_idx, translate_tag=True, maybe_unreachable=False):
231 def _lookup_commit(self, commit_id_or_idx, translate_tag=True, maybe_unreachable=False, reference_obj=None):
232
232 233 def is_null(value):
233 234 return len(value) == commit_id_or_idx.count('0')
234 235
@@ -239,21 +240,34 b' class GitRepository(BaseRepository):'
239 240 *map(safe_str, [commit_id_or_idx, self.name]))
240 241
241 242 is_bstr = isinstance(commit_id_or_idx, (str, unicode))
242 if ((is_bstr and commit_id_or_idx.isdigit() and len(commit_id_or_idx) < 12)
243 or isinstance(commit_id_or_idx, int) or is_null(commit_id_or_idx)):
244 try:
245 commit_id_or_idx = self.commit_ids[int(commit_id_or_idx)]
246 except Exception:
247 raise CommitDoesNotExistError(commit_missing_err)
243 is_branch = reference_obj and reference_obj.branch
248 244
249 elif is_bstr:
250 # Need to call remote to translate id for tagging scenario
245 lookup_ok = False
246 if is_bstr:
247 # Need to call remote to translate id for tagging scenarios,
248 # or branch that are numeric
251 249 try:
252 250 remote_data = self._remote.get_object(commit_id_or_idx,
253 251 maybe_unreachable=maybe_unreachable)
254 252 commit_id_or_idx = remote_data["commit_id"]
253 lookup_ok = True
255 254 except (CommitDoesNotExistError,):
256 raise CommitDoesNotExistError(commit_missing_err)
255 lookup_ok = False
256
257 if lookup_ok is False:
258 is_numeric_idx = \
259 (is_bstr and commit_id_or_idx.isdigit() and len(commit_id_or_idx) < 12) \
260 or isinstance(commit_id_or_idx, int)
261 if not is_branch and (is_numeric_idx or is_null(commit_id_or_idx)):
262 try:
263 commit_id_or_idx = self.commit_ids[int(commit_id_or_idx)]
264 lookup_ok = True
265 except Exception:
266 raise CommitDoesNotExistError(commit_missing_err)
267
268 # we failed regular lookup, and by integer number lookup
269 if lookup_ok is False:
270 raise CommitDoesNotExistError(commit_missing_err)
257 271
258 272 # Ensure we return full id
259 273 if not SHA_PATTERN.match(str(commit_id_or_idx)):
@@ -413,11 +427,12 b' class GitRepository(BaseRepository):'
413 427 return
414 428
415 429 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
416 translate_tag=True, maybe_unreachable=False):
430 translate_tag=True, maybe_unreachable=False, reference_obj=None):
417 431 """
418 432 Returns `GitCommit` object representing commit from git repository
419 433 at the given `commit_id` or head (most recent commit) if None given.
420 434 """
435
421 436 if self.is_empty():
422 437 raise EmptyRepositoryError("There are no commits yet")
423 438
@@ -443,7 +458,9 b' class GitRepository(BaseRepository):'
443 458 commit_id = "tip"
444 459
445 460 if translate_tag:
446 commit_id = self._lookup_commit(commit_id, maybe_unreachable=maybe_unreachable)
461 commit_id = self._lookup_commit(
462 commit_id, maybe_unreachable=maybe_unreachable,
463 reference_obj=reference_obj)
447 464
448 465 try:
449 466 idx = self._commit_ids[commit_id]
@@ -437,7 +437,7 b' class MercurialRepository(BaseRepository'
437 437 return os.path.join(self.path, '.hg', '.hgrc')
438 438
439 439 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
440 translate_tag=None, maybe_unreachable=False):
440 translate_tag=None, maybe_unreachable=False, reference_obj=None):
441 441 """
442 442 Returns ``MercurialCommit`` object representing repository's
443 443 commit at the given `commit_id` or `commit_idx`.
@@ -277,7 +277,7 b' class SubversionRepository(base.BaseRepo'
277 277 return os.path.join(self.path, 'hooks')
278 278
279 279 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
280 translate_tag=None, maybe_unreachable=False):
280 translate_tag=None, maybe_unreachable=False, reference_obj=None):
281 281 if self.is_empty():
282 282 raise EmptyRepositoryError("There are no commits yet")
283 283 if commit_id is not None:
@@ -468,7 +468,7 b' class FileNode(Node):'
468 468 mtype, encoding = db.guess_type(self.name)
469 469
470 470 if mtype is None:
471 if self.is_binary:
471 if not self.is_largefile() and self.is_binary:
472 472 mtype = 'application/octet-stream'
473 473 encoding = None
474 474 else:
@@ -839,6 +839,7 b' class LargeFileNode(FileNode):'
839 839 self.org_path = org_path
840 840 self.kind = NodeKind.LARGEFILE
841 841 self.alias = alias
842 self._content = ''
842 843
843 844 def _validate_path(self, path):
844 845 """
@@ -2398,10 +2398,10 b' class Repository(Base, BaseModel):'
2398 2398 # SCM PROPERTIES
2399 2399 #==========================================================================
2400 2400
2401 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2401 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False, reference_obj=None):
2402 2402 return get_commit_safe(
2403 2403 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2404 maybe_unreachable=maybe_unreachable)
2404 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
2405 2405
2406 2406 def get_changeset(self, rev=None, pre_load=None):
2407 2407 warnings.warn("Use get_commit", DeprecationWarning)
@@ -41,6 +41,8 b' class PermissionModel(BaseModel):'
41 41 """
42 42 Permissions model for RhodeCode
43 43 """
44 FORKING_DISABLED = 'hg.fork.none'
45 FORKING_ENABLED = 'hg.fork.repository'
44 46
45 47 cls = Permission
46 48 global_perms = {
@@ -122,8 +124,8 b' class PermissionModel(BaseModel):'
122 124 ('hg.repogroup.create.true', _('Enabled'))]
123 125
124 126 c_obj.fork_choices = [
125 ('hg.fork.none', _('Disabled')),
126 ('hg.fork.repository', _('Enabled'))]
127 (self.FORKING_DISABLED, _('Disabled')),
128 (self.FORKING_ENABLED, _('Enabled'))]
127 129
128 130 c_obj.inherit_default_permission_choices = [
129 131 ('hg.inherit_default_perms.false', _('Disabled')),
@@ -908,7 +908,8 b' class PullRequestModel(BaseModel):'
908 908
909 909 try:
910 910 if source_ref_type in self.REF_TYPES:
911 source_commit = source_repo.get_commit(source_ref_name)
911 source_commit = source_repo.get_commit(
912 source_ref_name, reference_obj=pull_request.source_ref_parts)
912 913 else:
913 914 source_commit = source_repo.get_commit(source_ref_id)
914 915 except CommitDoesNotExistError:
@@ -922,7 +923,8 b' class PullRequestModel(BaseModel):'
922 923
923 924 try:
924 925 if target_ref_type in self.REF_TYPES:
925 target_commit = target_repo.get_commit(target_ref_name)
926 target_commit = target_repo.get_commit(
927 target_ref_name, reference_obj=pull_request.target_ref_parts)
926 928 else:
927 929 target_commit = target_repo.get_commit(target_ref_id)
928 930 except CommitDoesNotExistError:
@@ -46,6 +46,7 b' from rhodecode.model.db import ('
46 46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 from rhodecode.model.permission import PermissionModel
49 50 from rhodecode.model.settings import VcsSettingsModel
50 51
51 52 log = logging.getLogger(__name__)
@@ -422,8 +423,15 b' class RepoModel(BaseModel):'
422 423 try:
423 424 cur_repo = self._get_repo(repo)
424 425 source_repo_name = cur_repo.repo_name
426
427 affected_user_ids = []
425 428 if 'user' in kwargs:
426 cur_repo.user = User.get_by_username(kwargs['user'])
429 old_owner_id = cur_repo.user.user_id
430 new_owner = User.get_by_username(kwargs['user'])
431 cur_repo.user = new_owner
432
433 if old_owner_id != new_owner.user_id:
434 affected_user_ids = [new_owner.user_id, old_owner_id]
427 435
428 436 if 'repo_group' in kwargs:
429 437 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
@@ -474,6 +482,9 b' class RepoModel(BaseModel):'
474 482 self._rename_filesystem_repo(
475 483 old=source_repo_name, new=new_name)
476 484
485 if affected_user_ids:
486 PermissionModel().trigger_permission_flush(affected_user_ids)
487
477 488 return cur_repo
478 489 except Exception:
479 490 log.error(traceback.format_exc())
@@ -39,6 +39,7 b' from rhodecode.model import BaseModel'
39 39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
40 40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
41 41 UserGroup, Repository)
42 from rhodecode.model.permission import PermissionModel
42 43 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 44 from rhodecode.lib.caching_query import FromCache
44 45 from rhodecode.lib.utils2 import action_logger_generic
@@ -531,8 +532,14 b' class RepoGroupModel(BaseModel):'
531 532
532 533 new_path = repo_group.full_path
533 534
535 affected_user_ids = []
534 536 if 'user' in form_data:
535 repo_group.user = User.get_by_username(form_data['user'])
537 old_owner_id = repo_group.user.user_id
538 new_owner = User.get_by_username(form_data['user'])
539 repo_group.user = new_owner
540
541 if old_owner_id != new_owner.user_id:
542 affected_user_ids = [new_owner.user_id, old_owner_id]
536 543
537 544 self.sa.add(repo_group)
538 545
@@ -566,6 +573,9 b' class RepoGroupModel(BaseModel):'
566 573 # Trigger update event.
567 574 events.trigger(events.RepoGroupUpdateEvent(repo_group))
568 575
576 if affected_user_ids:
577 PermissionModel().trigger_permission_flush(affected_user_ids)
578
569 579 return repo_group
570 580 except Exception:
571 581 log.error(traceback.format_exc())
@@ -12,6 +12,12 b''
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 pyroutes.register('admin_artifacts', '/_admin/artifacts', []);
16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
15 21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
16 22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
17 23 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
@@ -1331,7 +1331,7 b' var CommentsController = function() {'
1331 1331
1332 1332 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1333 1333 if ($comments.length===0) {
1334 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no)
1334 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(escapeHtml(f_path), line_no)
1335 1335 var $reply_container = $('#cb-comments-inline-container-template')
1336 1336 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1337 1337 $td.append($($reply_container).html());
@@ -115,6 +115,7 b''
115 115 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
116 116 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
117 117 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
118 <li class="${h.is_active('artifacts', active)}"><a href="${h.route_path('admin_artifacts')}">${_('Artifacts')}</a></li>
118 119 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
119 120 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
120 121 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
@@ -62,7 +62,7 b''
62 62 <div class="radios">
63 63 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
64 64 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
65 <span class="help-block">${_('Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups.')}</span>
65 <span class="help-block">${_('Permission to create repository forks. Root level forks will only work if repository creation is enabled.')}</span>
66 66 </div>
67 67 </div>
68 68 <div class="field">
@@ -24,7 +24,7 b''
24 24 ## to speed up lookups cache some functions before the loop
25 25 <%
26 26 active_patterns = h.get_active_pattern_entries(c.repo_name)
27 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns, issues_container=getattr(c, 'referenced_commit_issues', None))
27 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
28 28 %>
29 29
30 30 %for commit in c.commit_ranges:
@@ -57,7 +57,7 b''
57 57 </td>
58 58 <td class="mid td-description">
59 59 <div class="log-container truncate-wrap">
60 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${urlify_commit_message(commit.message, c.repo_name)}</div>
60 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${urlify_commit_message(commit.message, c.repo_name, issues_container_callback=getattr(c, 'referenced_commit_issues', h.IssuesRegistry())(commit.serialize()))}</div>
61 61 </div>
62 62 </td>
63 63 </tr>
@@ -416,6 +416,12 b''
416 416 </a>
417 417 </%def>
418 418
419 <%def name="repo_artifact_admin_name(file_uid, artifact_display_name)">
420 <a href="${h.route_path('admin_artifacts_show_info', uid=file_uid)}">
421 ${(artifact_display_name or '_EMPTY_NAME_')}
422 </a>
423 </%def>
424
419 425 <%def name="repo_artifact_uid(repo_name, file_uid)">
420 426 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
421 427 </%def>
@@ -443,6 +449,7 b''
443 449 % endif
444 450 </%def>
445 451
452
446 453 <%def name="markup_form(form_id, form_text='', help_text=None)">
447 454
448 455 <div class="markup-form">
@@ -221,7 +221,7 b' if (show_disabled) {'
221 221 <%= version_info %>
222 222 <% } %>
223 223 <br/>
224 File: <code><%- file_name -%></code>
224 File: <code><%= file_name -%></code>
225 225 <% } else { %>
226 226 <% if (review_status) { %>
227 227 <i class="icon-circle review-status-<%= review_status %>"></i>
@@ -27,8 +27,8 b''
27 27 <%def name="main()">
28 28 ## Container to gather extracted Tickets
29 29 <%
30 c.referenced_commit_issues = []
31 c.referenced_desc_issues = []
30 c.referenced_commit_issues = h.IssuesRegistry()
31 c.referenced_desc_issues = h.IssuesRegistry()
32 32 %>
33 33
34 34 <script type="text/javascript">
@@ -86,7 +86,7 b''
86 86 </div>
87 87
88 88 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
89 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
89 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container_callback=c.referenced_desc_issues())}
90 90 </div>
91 91
92 92 <div id="pr-desc-edit" class="input textarea" style="display: none;">
@@ -435,7 +435,7 b''
435 435 </td>
436 436 <td class="mid td-description">
437 437 <div class="log-container truncate-wrap">
438 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
438 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container_callback=c.referenced_commit_issues(commit.serialize()))}</div>
439 439 </div>
440 440 </td>
441 441 </tr>
@@ -809,7 +809,7 b''
809 809 <div class="sidebar-element clear-both">
810 810 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
811 811 <i class="icon-info-circled"></i>
812 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
812 ${(c.referenced_desc_issues.issues_unique_count + c.referenced_commit_issues.issues_unique_count)}
813 813 </div>
814 814
815 815 <div class="right-sidebar-expanded-state pr-details-title">
@@ -822,15 +822,17 b''
822 822 <table>
823 823
824 824 <tr><td><code>${_('In pull request description')}:</code></td></tr>
825 % if c.referenced_desc_issues:
826 % for ticket_dict in sorted(c.referenced_desc_issues):
825 % if c.referenced_desc_issues.issues:
826
827 % for ticket_id, ticket_dict in c.referenced_desc_issues.unique_issues.items():
827 828 <tr>
828 829 <td>
829 <a href="${ticket_dict.get('url')}">
830 ${ticket_dict.get('id')}
830 <a href="${ticket_dict[0].get('url')}">
831 ${ticket_id}
831 832 </a>
832 833 </td>
833 834 </tr>
835
834 836 % endfor
835 837 % else:
836 838 <tr>
@@ -841,13 +843,14 b''
841 843 % endif
842 844
843 845 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
844 % if c.referenced_commit_issues:
845 % for ticket_dict in sorted(c.referenced_commit_issues):
846 % if c.referenced_commit_issues.issues:
847 % for ticket_id, ticket_dict in c.referenced_commit_issues.unique_issues.items():
846 848 <tr>
847 849 <td>
848 <a href="${ticket_dict.get('url')}">
849 ${ticket_dict.get('id')}
850 <a href="${ticket_dict[0].get('url')}">
851 ${ticket_id}
850 852 </a>
853 - ${_ungettext('in %s commit', 'in %s commits', len(ticket_dict)) % (len(ticket_dict))}
851 854 </td>
852 855 </tr>
853 856 % endfor
@@ -95,7 +95,8 b''
95 95 var fname = selectedReference.raw_id + ext;
96 96 var href = pyroutes.url('repo_archivefile', {
97 97 'repo_name': templateContext.repo_name,
98 'fname': fname
98 'fname': fname,
99 'with_hash': '1'
99 100 });
100 101 // set new label
101 102 $(this).html(ico + ' {0}{1}'.format(escapeHtml(e.added.text), ext));
General Comments 1
Under Review
author

Auto status change to "Under Review"

You need to be logged in to leave comments. Login now