##// 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 [bumpversion]
1 [bumpversion]
2 current_version = 4.24.1
2 current_version = 4.25.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:rhodecode/VERSION]
5 [bumpversion:file:rhodecode/VERSION]
6
@@ -5,25 +5,20 b' done = false'
5 done = true
5 done = true
6
6
7 [task:rc_tools_pinned]
7 [task:rc_tools_pinned]
8 done = true
9
8
10 [task:fixes_on_stable]
9 [task:fixes_on_stable]
11 done = true
12
10
13 [task:pip2nix_generated]
11 [task:pip2nix_generated]
14 done = true
15
12
16 [task:changelog_updated]
13 [task:changelog_updated]
17 done = true
18
14
19 [task:generate_api_docs]
15 [task:generate_api_docs]
20 done = true
16
17 [task:updated_translation]
21
18
22 [release]
19 [release]
23 state = prepared
20 state = in_progress
24 version = 4.24.1
21 version = 4.25.0
25
26 [task:updated_translation]
27
22
28 [task:generate_js_routes]
23 [task:generate_js_routes]
29
24
@@ -130,6 +130,24 b' file_store_get_info (EE only)'
130 error : null
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 file_store_add_metadata (EE only)
151 file_store_add_metadata (EE only)
134 ---------------------------------
152 ---------------------------------
135
153
@@ -9,6 +9,7 b' Release Notes'
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12 release-notes-4.25.0.rst
12 release-notes-4.24.1.rst
13 release-notes-4.24.1.rst
13 release-notes-4.24.0.rst
14 release-notes-4.24.0.rst
14 release-notes-4.23.2.rst
15 release-notes-4.23.2.rst
@@ -6,7 +6,7 b' diff -rup pytest-4.6.5-orig/setup.py pyt'
6 setup(
6 setup(
7 use_scm_version={"write_to": "src/_pytest/_version.py"},
7 use_scm_version={"write_to": "src/_pytest/_version.py"},
8 - setup_requires=["setuptools-scm", "setuptools>=40.0"],
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 package_dir={"": "src"},
10 package_dir={"": "src"},
11 # fmt: off
11 # fmt: off
12 extras_require={ No newline at end of file
12 extras_require={
@@ -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 # Avoid that base packages screw up the build process
349 # Avoid that base packages screw up the build process
284 inherit (basePythonPackages)
350 inherit (basePythonPackages)
285 setuptools;
351 setuptools;
@@ -1883,7 +1883,7 b' self: super: {'
1883 };
1883 };
1884 };
1884 };
1885 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1885 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1886 name = "rhodecode-enterprise-ce-4.24.1";
1886 name = "rhodecode-enterprise-ce-4.25.0";
1887 buildInputs = [
1887 buildInputs = [
1888 self."pytest"
1888 self."pytest"
1889 self."py"
1889 self."py"
@@ -2092,6 +2092,17 b' self: super: {'
2092 license = [ pkgs.lib.licenses.mit ];
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 "simplegeneric" = super.buildPythonPackage {
2106 "simplegeneric" = super.buildPythonPackage {
2096 name = "simplegeneric-0.8.1";
2107 name = "simplegeneric-0.8.1";
2097 doCheck = false;
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 validate_repo_permissions(apiuser, repoid, repo, _perms)
1212 validate_repo_permissions(apiuser, repoid, repo, _perms)
1213
1213
1214 # check if the regular user has at least fork permissions as well
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 raise JSONRPCForbidden()
1216 raise JSONRPCForbidden()
1217
1217
1218 # check if user can set owner parameter
1218 # check if user can set owner parameter
@@ -255,7 +255,7 b' class LocalFileStorage(object):'
255
255
256 return filename, metadata
256 return filename, metadata
257
257
258 def get_metadata(self, filename):
258 def get_metadata(self, filename, ignore_missing=False):
259 """
259 """
260 Reads JSON stored metadata for a file
260 Reads JSON stored metadata for a file
261
261
@@ -264,6 +264,7 b' class LocalFileStorage(object):'
264 """
264 """
265 filename = self.store_path(filename)
265 filename = self.store_path(filename)
266 filename_meta = filename + '.meta'
266 filename_meta = filename + '.meta'
267
267 if ignore_missing and not os.path.isfile(filename_meta):
268 return {}
268 with open(filename_meta, "rb") as source_meta:
269 with open(filename_meta, "rb") as source_meta:
269 return json.loads(source_meta.read())
270 return json.loads(source_meta.read())
@@ -932,8 +932,8 b' def includeme(config):'
932 name='edit_repo_perms_branch',
932 name='edit_repo_perms_branch',
933 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
933 pattern='/{repo_name:.*?[^/]}/settings/branch_permissions', repo_route=True)
934 config.add_view(
934 config.add_view(
935 RepoBranchesView,
935 RepoSettingsBranchPermissionsView,
936 attr='branches',
936 attr='branch_permissions',
937 route_name='edit_repo_perms_branch', request_method='GET',
937 route_name='edit_repo_perms_branch', request_method='GET',
938 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
938 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
939
939
@@ -950,8 +950,8 b' def includeme(config):'
950 config.add_view(
950 config.add_view(
951 RepoMaintenanceView,
951 RepoMaintenanceView,
952 attr='repo_maintenance',
952 attr='repo_maintenance',
953 route_name='edit_repo_maintenance_execute', request_method='GET',
953 route_name='edit_repo_maintenance', request_method='GET',
954 renderer='json', xhr=True)
954 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
955
955
956 config.add_route(
956 config.add_route(
957 name='edit_repo_maintenance_execute',
957 name='edit_repo_maintenance_execute',
@@ -959,8 +959,8 b' def includeme(config):'
959 config.add_view(
959 config.add_view(
960 RepoMaintenanceView,
960 RepoMaintenanceView,
961 attr='repo_maintenance_execute',
961 attr='repo_maintenance_execute',
962 route_name='edit_repo_maintenance', request_method='GET',
962 route_name='edit_repo_maintenance_execute', request_method='GET',
963 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
963 renderer='json', xhr=True)
964
964
965 # Fields
965 # Fields
966 config.add_route(
966 config.add_route(
@@ -542,6 +542,28 b' class TestRepositoryArchival(object):'
542 for header in headers:
542 for header in headers:
543 assert header in response.headers.items()
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 @pytest.mark.parametrize('arch_ext',[
567 @pytest.mark.parametrize('arch_ext',[
546 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
568 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
547 def test_archival_wrong_ext(self, backend, arch_ext):
569 def test_archival_wrong_ext(self, backend, arch_ext):
@@ -34,7 +34,7 b' from rhodecode.lib.auth import ('
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.graphmod import _colored, _dagwalker
35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 from rhodecode.lib.helpers import RepoPage
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 from rhodecode.lib.vcs.exceptions import (
38 from rhodecode.lib.vcs.exceptions import (
39 RepositoryError, CommitDoesNotExistError,
39 RepositoryError, CommitDoesNotExistError,
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
@@ -110,7 +110,7 b' class RepoChangelogView(RepoAppView):'
110
110
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
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 category='warning')
114 category='warning')
115 redirect_url = h.route_path(
115 redirect_url = h.route_path(
116 'repo_commits_file', repo_name=repo_name,
116 'repo_commits_file', repo_name=repo_name,
@@ -674,6 +674,10 b' class RepoCommitsView(RepoAppView):'
674 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
674 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
675 comment_repo_admin = is_repo_admin and is_repo_comment
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 if super_admin or comment_owner or comment_repo_admin:
681 if super_admin or comment_owner or comment_repo_admin:
678 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
682 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
679 Session().commit()
683 Session().commit()
@@ -104,6 +104,9 b' class RepoFeedView(RepoAppView):'
104
104
105 def _get_commits(self):
105 def _get_commits(self):
106 pre_load = ['author', 'branch', 'date', 'message', 'parents']
106 pre_load = ['author', 'branch', 'date', 'message', 'parents']
107 if self.rhodecode_vcs_repo.is_empty():
108 return []
109
107 collection = self.rhodecode_vcs_repo.get_commits(
110 collection = self.rhodecode_vcs_repo.get_commits(
108 branch_name=None, show_hidden=False, pre_load=pre_load,
111 branch_name=None, show_hidden=False, pre_load=pre_load,
109 translate_tags=False)
112 translate_tags=False)
@@ -137,6 +140,7 b' class RepoFeedView(RepoAppView):'
137 language=self.language,
140 language=self.language,
138 ttl=self.ttl
141 ttl=self.ttl
139 )
142 )
143
140 for commit in reversed(self._get_commits()):
144 for commit in reversed(self._get_commits()):
141 date = self._set_timezone(commit.date)
145 date = self._set_timezone(commit.date)
142 feed.add_item(
146 feed.add_item(
@@ -325,17 +325,18 b' class RepoFilesView(RepoAppView):'
325
325
326 return lf_enabled
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 # original backward compat name of archive
329 # original backward compat name of archive
330 clean_name = safe_str(db_repo_name.replace('/', '_'))
330 clean_name = safe_str(db_repo_name.replace('/', '_'))
331
331
332 # e.g vcsserver.zip
332 # e.g vcsserver.zip
333 # e.g vcsserver-abcdefgh.zip
333 # e.g vcsserver-abcdefgh.zip
334 # e.g vcsserver-abcdefgh-defghijk.zip
334 # e.g vcsserver-abcdefgh-defghijk.zip
335 archive_name = '{}{}{}{}{}'.format(
335 archive_name = '{}{}{}{}{}{}'.format(
336 clean_name,
336 clean_name,
337 '-sub' if subrepos else '',
337 '-sub' if subrepos else '',
338 commit_sha,
338 commit_sha,
339 '-{}'.format('plain') if not with_hash else '',
339 '-{}'.format(path_sha) if path_sha else '',
340 '-{}'.format(path_sha) if path_sha else '',
340 ext)
341 ext)
341 return archive_name
342 return archive_name
@@ -372,6 +373,11 b' class RepoFilesView(RepoAppView):'
372 except EmptyRepositoryError:
373 except EmptyRepositoryError:
373 return Response(_('Empty repository'))
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 try:
381 try:
376 at_path = commit.get_node(at_path).path or default_at_path
382 at_path = commit.get_node(at_path).path or default_at_path
377 except Exception:
383 except Exception:
@@ -385,7 +391,7 b' class RepoFilesView(RepoAppView):'
385 # used for cache etc
391 # used for cache etc
386 archive_name = self._get_archive_name(
392 archive_name = self._get_archive_name(
387 self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos,
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 if not with_hash:
396 if not with_hash:
391 short_sha = ''
397 short_sha = ''
@@ -394,7 +400,7 b' class RepoFilesView(RepoAppView):'
394 # what end client gets served
400 # what end client gets served
395 response_archive_name = self._get_archive_name(
401 response_archive_name = self._get_archive_name(
396 self.db_repo_name, commit_sha=short_sha, ext=ext, subrepos=subrepos,
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 # remove extension from our archive directory name
404 # remove extension from our archive directory name
399 archive_dir_name = response_archive_name[:-len(ext)]
405 archive_dir_name = response_archive_name[:-len(ext)]
400
406
@@ -404,9 +410,10 b' class RepoFilesView(RepoAppView):'
404 cached_archive_path = None
410 cached_archive_path = None
405
411
406 if archive_cache_enabled:
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 if not os.path.isdir(CONFIG['archive_cache_dir']):
414 if not os.path.isdir(CONFIG['archive_cache_dir']):
409 os.makedirs(CONFIG['archive_cache_dir'])
415 os.makedirs(CONFIG['archive_cache_dir'])
416
410 cached_archive_path = os.path.join(
417 cached_archive_path = os.path.join(
411 CONFIG['archive_cache_dir'], archive_name)
418 CONFIG['archive_cache_dir'], archive_name)
412 if os.path.isfile(cached_archive_path):
419 if os.path.isfile(cached_archive_path):
@@ -165,7 +165,7 b' class RepoForksView(RepoAppView, DataGri'
165
165
166 @LoginRequired()
166 @LoginRequired()
167 @NotAnonymous()
167 @NotAnonymous()
168 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
168 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
169 @HasRepoPermissionAnyDecorator(
169 @HasRepoPermissionAnyDecorator(
170 'repository.read', 'repository.write', 'repository.admin')
170 'repository.read', 'repository.write', 'repository.admin')
171 def repo_fork_new(self):
171 def repo_fork_new(self):
@@ -191,7 +191,7 b' class RepoForksView(RepoAppView, DataGri'
191
191
192 @LoginRequired()
192 @LoginRequired()
193 @NotAnonymous()
193 @NotAnonymous()
194 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
194 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
195 @HasRepoPermissionAnyDecorator(
195 @HasRepoPermissionAnyDecorator(
196 'repository.read', 'repository.write', 'repository.admin')
196 'repository.read', 'repository.write', 'repository.admin')
197 @CSRFRequired()
197 @CSRFRequired()
@@ -1748,6 +1748,10 b' class RepoPullRequestsView(RepoAppView, '
1748 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1748 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1749 comment_repo_admin = is_repo_admin and is_repo_comment
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 if super_admin or comment_owner or comment_repo_admin:
1755 if super_admin or comment_owner or comment_repo_admin:
1752 old_calculated_status = comment.pull_request.calculated_review_status()
1756 old_calculated_status = comment.pull_request.calculated_review_status()
1753 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1757 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
@@ -89,6 +89,14 b' class SshWrapper(object):'
89
89
90 return conn
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 def get_repo_details(self, mode):
100 def get_repo_details(self, mode):
93 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
101 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
94 repo_name = None
102 repo_name = None
@@ -97,14 +105,14 b' class SshWrapper(object):'
97 hg_match = re.match(hg_pattern, self.command)
105 hg_match = re.match(hg_pattern, self.command)
98 if hg_match is not None:
106 if hg_match is not None:
99 vcs_type = 'hg'
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 return vcs_type, repo_name, mode
109 return vcs_type, repo_name, mode
102
110
103 git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$'
111 git_pattern = r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$'
104 git_match = re.match(git_pattern, self.command)
112 git_match = re.match(git_pattern, self.command)
105 if git_match is not None:
113 if git_match is not None:
106 vcs_type = 'git'
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 mode = git_match.group(1)
116 mode = git_match.group(1)
109 return vcs_type, repo_name, mode
117 return vcs_type, repo_name, mode
110
118
@@ -19,6 +19,8 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22 import os
23
22 import mock
24 import mock
23 import pytest
25 import pytest
24
26
@@ -107,6 +109,8 b' class TestGitServer(object):'
107 def test_run_returns_executes_command(self, git_server):
109 def test_run_returns_executes_command(self, git_server):
108 server = git_server.create()
110 server = git_server.create()
109 from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper
111 from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper
112
113 os.environ['SSH_CLIENT'] = '127.0.0.1'
110 with mock.patch.object(GitTunnelWrapper, 'create_hooks_env') as _patch:
114 with mock.patch.object(GitTunnelWrapper, 'create_hooks_env') as _patch:
111 _patch.return_value = 0
115 _patch.return_value = 0
112 with mock.patch.object(GitTunnelWrapper, 'command', return_value='date'):
116 with mock.patch.object(GitTunnelWrapper, 'command', return_value='date'):
@@ -108,6 +108,7 b' class TestMercurialServer(object):'
108 def test_run_returns_executes_command(self, hg_server):
108 def test_run_returns_executes_command(self, hg_server):
109 server = hg_server.create()
109 server = hg_server.create()
110 from rhodecode.apps.ssh_support.lib.backends.hg import MercurialTunnelWrapper
110 from rhodecode.apps.ssh_support.lib.backends.hg import MercurialTunnelWrapper
111 os.environ['SSH_CLIENT'] = '127.0.0.1'
111 with mock.patch.object(MercurialTunnelWrapper, 'create_hooks_env') as _patch:
112 with mock.patch.object(MercurialTunnelWrapper, 'create_hooks_env') as _patch:
112 _patch.return_value = 0
113 _patch.return_value = 0
113 with mock.patch.object(MercurialTunnelWrapper, 'command', return_value='date'):
114 with mock.patch.object(MercurialTunnelWrapper, 'command', return_value='date'):
@@ -17,7 +17,7 b''
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 import os
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
@@ -174,6 +174,7 b' class TestSubversionServer(object):'
174 def test_run_returns_executes_command(self, svn_server):
174 def test_run_returns_executes_command(self, svn_server):
175 server = svn_server.create()
175 server = svn_server.create()
176 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
176 from rhodecode.apps.ssh_support.lib.backends.svn import SubversionTunnelWrapper
177 os.environ['SSH_CLIENT'] = '127.0.0.1'
177 with mock.patch.object(
178 with mock.patch.object(
178 SubversionTunnelWrapper, 'get_first_client_response',
179 SubversionTunnelWrapper, 'get_first_client_response',
179 return_value={'url': 'http://server/test-svn'}):
180 return_value={'url': 'http://server/test-svn'}):
@@ -32,6 +32,7 b' import json'
32 from rhodecode.lib import diffs
32 from rhodecode.lib import diffs
33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 from rhodecode.lib.vcs.backends.git.diff import GitDiff
34 from rhodecode.lib.vcs.backends.git.diff import GitDiff
35 from vcsserver.utils import safe_int
35
36
36
37
37 def get_svn_files(repo, vcs_repo, refs):
38 def get_svn_files(repo, vcs_repo, refs):
@@ -74,7 +75,7 b' def get_svn_files(repo, vcs_repo, refs):'
74 # skip dirs
75 # skip dirs
75 continue
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 files.append(parsed_entry)
80 files.append(parsed_entry)
80
81
@@ -524,8 +524,10 b' class PermissionCalculator(object):'
524
524
525 # In case we want to extend this list we should make sure
525 # In case we want to extend this list we should make sure
526 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
526 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
527 from rhodecode.model.permission import PermissionModel
528
527 _configurable = frozenset([
529 _configurable = frozenset([
528 'hg.fork.none', 'hg.fork.repository',
530 PermissionModel.FORKING_DISABLED, PermissionModel.FORKING_ENABLED,
529 'hg.create.none', 'hg.create.repository',
531 'hg.create.none', 'hg.create.repository',
530 'hg.usergroup.create.false', 'hg.usergroup.create.true',
532 'hg.usergroup.create.false', 'hg.usergroup.create.true',
531 'hg.repogroup.create.false', 'hg.repogroup.create.true',
533 'hg.repogroup.create.false', 'hg.repogroup.create.true',
@@ -25,6 +25,7 b' Consists of functions to typically be us'
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27 import base64
27 import base64
28 import collections
28
29
29 import os
30 import os
30 import random
31 import random
@@ -1733,7 +1734,7 b' def process_patterns(text_string, repo_n'
1733
1734
1734
1735
1735 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None,
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 Parses given text message and makes proper links.
1739 Parses given text message and makes proper links.
1739 issues are linked to given issue-server, and rest is a commit link
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 new_text, issues, errors = process_patterns(
1757 new_text, issues, errors = process_patterns(
1757 new_text, repository or '', active_entries=active_pattern_entries)
1758 new_text, repository or '', active_entries=active_pattern_entries)
1758
1759
1759 if issues_container is not None:
1760 if issues_container_callback is not None:
1760 issues_container.extend(issues)
1761 for issue in issues:
1762 issues_container_callback(issue)
1761
1763
1762 if error_container is not None:
1764 if error_container is not None:
1763 error_container.extend(errors)
1765 error_container.extend(errors)
@@ -1802,7 +1804,7 b' def renderer_from_filename(filename, exc'
1802
1804
1803
1805
1804 def render(source, renderer='rst', mentions=False, relative_urls=None,
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 def maybe_convert_relative_links(html_source):
1809 def maybe_convert_relative_links(html_source):
1808 if relative_urls:
1810 if relative_urls:
@@ -1819,8 +1821,9 b" def render(source, renderer='rst', menti"
1819 source, issues, errors = process_patterns(
1821 source, issues, errors = process_patterns(
1820 source, repo_name, link_format='rst',
1822 source, repo_name, link_format='rst',
1821 active_entries=active_pattern_entries)
1823 active_entries=active_pattern_entries)
1822 if issues_container is not None:
1824 if issues_container_callback is not None:
1823 issues_container.extend(issues)
1825 for issue in issues:
1826 issues_container_callback(issue)
1824
1827
1825 return literal(
1828 return literal(
1826 '<div class="rst-block">%s</div>' %
1829 '<div class="rst-block">%s</div>' %
@@ -1833,8 +1836,10 b" def render(source, renderer='rst', menti"
1833 source, issues, errors = process_patterns(
1836 source, issues, errors = process_patterns(
1834 source, repo_name, link_format='markdown',
1837 source, repo_name, link_format='markdown',
1835 active_entries=active_pattern_entries)
1838 active_entries=active_pattern_entries)
1836 if issues_container is not None:
1839 if issues_container_callback is not None:
1837 issues_container.extend(issues)
1840 for issue in issues:
1841 issues_container_callback(issue)
1842
1838
1843
1839 return literal(
1844 return literal(
1840 '<div class="markdown-block">%s</div>' %
1845 '<div class="markdown-block">%s</div>' %
@@ -2115,3 +2120,29 b' def is_active(menu_entry, selected):'
2115
2120
2116 if selected in menu_entry:
2121 if selected in menu_entry:
2117 return "active"
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 # List of path views first chunk we don't do any checks
161 # List of path views first chunk we don't do any checks
162 white_list = [
162 white_list = [
163 # e.g /_file_store/download
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 path_info = environ['PATH_INFO']
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 log.debug('path `%s` in whitelist, skipping...', path_info)
178 log.debug('path `%s` in whitelist, skipping...', path_info)
171 return handler
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 if VCS_TYPE_KEY in environ:
186 if VCS_TYPE_KEY in environ:
174 raw_type = environ[VCS_TYPE_KEY]
187 raw_type = environ[VCS_TYPE_KEY]
175 if raw_type == VCS_TYPE_SKIP:
188 if raw_type == VCS_TYPE_SKIP:
@@ -181,7 +194,7 b' def detect_vcs_request(environ, backends'
181 log.debug('got handler:%s from environ', handler)
194 log.debug('got handler:%s from environ', handler)
182
195
183 if not handler:
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 for vcs_type in backends:
198 for vcs_type in backends:
186 vcs_check, _handler = checks[vcs_type]
199 vcs_check, _handler = checks[vcs_type]
187 if vcs_check(environ):
200 if vcs_check(environ):
@@ -594,6 +594,9 b' def credentials_filter(uri):'
594 :param uri:
594 :param uri:
595 """
595 """
596 import urlobject
596 import urlobject
597 if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue):
598 return 'InvalidDecryptionKey'
599
597 url_obj = urlobject.URLObject(cleaned_uri(uri))
600 url_obj = urlobject.URLObject(cleaned_uri(uri))
598 url_obj = url_obj.without_password().without_username()
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 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
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 Safe version of get_commit if this commit doesn't exists for a
663 Safe version of get_commit if this commit doesn't exists for a
661 repository it returns a Dummy one instead
664 repository it returns a Dummy one instead
@@ -665,6 +668,7 b' def get_commit_safe(repo, commit_id=None'
665 :param commit_idx: numeric commit index
668 :param commit_idx: numeric commit index
666 :param pre_load: optional list of commit attributes to load
669 :param pre_load: optional list of commit attributes to load
667 :param maybe_unreachable: translate unreachable commits on git repos
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 # TODO(skreft): remove these circular imports
673 # TODO(skreft): remove these circular imports
670 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
674 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
@@ -676,7 +680,7 b' def get_commit_safe(repo, commit_id=None'
676 try:
680 try:
677 commit = repo.get_commit(
681 commit = repo.get_commit(
678 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
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 except (RepositoryError, LookupError):
684 except (RepositoryError, LookupError):
681 commit = EmptyCommit()
685 commit = EmptyCommit()
682 return commit
686 return commit
@@ -72,6 +72,10 b' class Reference(_Reference):'
72 if self.type == 'book':
72 if self.type == 'book':
73 return self.name
73 return self.name
74
74
75 @property
76 def to_unicode(self):
77 return reference_to_unicode(self)
78
75
79
76 def unicode_to_reference(raw):
80 def unicode_to_reference(raw):
77 """
81 """
@@ -483,7 +487,7 b' class BaseRepository(object):'
483 self._is_empty = False
487 self._is_empty = False
484
488
485 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
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 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
492 Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx`
489 are both None, most recent commit is returned.
493 are both None, most recent commit is returned.
@@ -98,7 +98,7 b' class GitCommit(base.BaseCommit):'
98 elif attr == "parents":
98 elif attr == "parents":
99 value = self._make_commits(value)
99 value = self._make_commits(value)
100 elif attr == "branch":
100 elif attr == "branch":
101 value = value[0] if value else None
101 value = self._set_branch(value)
102 self.__dict__[attr] = value
102 self.__dict__[attr] = value
103
103
104 @LazyProperty
104 @LazyProperty
@@ -156,13 +156,15 b' class GitCommit(base.BaseCommit):'
156 branches.append(name)
156 branches.append(name)
157 return branches
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 @LazyProperty
164 @LazyProperty
160 def branch(self):
165 def branch(self):
161 branches = self._remote.branch(self.raw_id)
166 branches = self._remote.branch(self.raw_id)
162
167 return self._set_branch(branches)
163 if branches:
164 # actually commit can have multiple branches in git
165 return safe_unicode(branches[0])
166
168
167 def _get_tree_id_for_path(self, path):
169 def _get_tree_id_for_path(self, path):
168 path = safe_str(path)
170 path = safe_str(path)
@@ -228,7 +228,8 b' class GitRepository(BaseRepository):'
228 return []
228 return []
229 return output.splitlines()
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 def is_null(value):
233 def is_null(value):
233 return len(value) == commit_id_or_idx.count('0')
234 return len(value) == commit_id_or_idx.count('0')
234
235
@@ -239,21 +240,34 b' class GitRepository(BaseRepository):'
239 *map(safe_str, [commit_id_or_idx, self.name]))
240 *map(safe_str, [commit_id_or_idx, self.name]))
240
241
241 is_bstr = isinstance(commit_id_or_idx, (str, unicode))
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 is_branch = reference_obj and reference_obj.branch
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)
248
244
249 elif is_bstr:
245 lookup_ok = False
250 # Need to call remote to translate id for tagging scenario
246 if is_bstr:
247 # Need to call remote to translate id for tagging scenarios,
248 # or branch that are numeric
251 try:
249 try:
252 remote_data = self._remote.get_object(commit_id_or_idx,
250 remote_data = self._remote.get_object(commit_id_or_idx,
253 maybe_unreachable=maybe_unreachable)
251 maybe_unreachable=maybe_unreachable)
254 commit_id_or_idx = remote_data["commit_id"]
252 commit_id_or_idx = remote_data["commit_id"]
253 lookup_ok = True
255 except (CommitDoesNotExistError,):
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 # Ensure we return full id
272 # Ensure we return full id
259 if not SHA_PATTERN.match(str(commit_id_or_idx)):
273 if not SHA_PATTERN.match(str(commit_id_or_idx)):
@@ -413,11 +427,12 b' class GitRepository(BaseRepository):'
413 return
427 return
414
428
415 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
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 Returns `GitCommit` object representing commit from git repository
432 Returns `GitCommit` object representing commit from git repository
419 at the given `commit_id` or head (most recent commit) if None given.
433 at the given `commit_id` or head (most recent commit) if None given.
420 """
434 """
435
421 if self.is_empty():
436 if self.is_empty():
422 raise EmptyRepositoryError("There are no commits yet")
437 raise EmptyRepositoryError("There are no commits yet")
423
438
@@ -443,7 +458,9 b' class GitRepository(BaseRepository):'
443 commit_id = "tip"
458 commit_id = "tip"
444
459
445 if translate_tag:
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 try:
465 try:
449 idx = self._commit_ids[commit_id]
466 idx = self._commit_ids[commit_id]
@@ -437,7 +437,7 b' class MercurialRepository(BaseRepository'
437 return os.path.join(self.path, '.hg', '.hgrc')
437 return os.path.join(self.path, '.hg', '.hgrc')
438
438
439 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
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 Returns ``MercurialCommit`` object representing repository's
442 Returns ``MercurialCommit`` object representing repository's
443 commit at the given `commit_id` or `commit_idx`.
443 commit at the given `commit_id` or `commit_idx`.
@@ -277,7 +277,7 b' class SubversionRepository(base.BaseRepo'
277 return os.path.join(self.path, 'hooks')
277 return os.path.join(self.path, 'hooks')
278
278
279 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
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 if self.is_empty():
281 if self.is_empty():
282 raise EmptyRepositoryError("There are no commits yet")
282 raise EmptyRepositoryError("There are no commits yet")
283 if commit_id is not None:
283 if commit_id is not None:
@@ -468,7 +468,7 b' class FileNode(Node):'
468 mtype, encoding = db.guess_type(self.name)
468 mtype, encoding = db.guess_type(self.name)
469
469
470 if mtype is None:
470 if mtype is None:
471 if self.is_binary:
471 if not self.is_largefile() and self.is_binary:
472 mtype = 'application/octet-stream'
472 mtype = 'application/octet-stream'
473 encoding = None
473 encoding = None
474 else:
474 else:
@@ -839,6 +839,7 b' class LargeFileNode(FileNode):'
839 self.org_path = org_path
839 self.org_path = org_path
840 self.kind = NodeKind.LARGEFILE
840 self.kind = NodeKind.LARGEFILE
841 self.alias = alias
841 self.alias = alias
842 self._content = ''
842
843
843 def _validate_path(self, path):
844 def _validate_path(self, path):
844 """
845 """
@@ -2398,10 +2398,10 b' class Repository(Base, BaseModel):'
2398 # SCM PROPERTIES
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 return get_commit_safe(
2402 return get_commit_safe(
2403 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
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 def get_changeset(self, rev=None, pre_load=None):
2406 def get_changeset(self, rev=None, pre_load=None):
2407 warnings.warn("Use get_commit", DeprecationWarning)
2407 warnings.warn("Use get_commit", DeprecationWarning)
@@ -41,6 +41,8 b' class PermissionModel(BaseModel):'
41 """
41 """
42 Permissions model for RhodeCode
42 Permissions model for RhodeCode
43 """
43 """
44 FORKING_DISABLED = 'hg.fork.none'
45 FORKING_ENABLED = 'hg.fork.repository'
44
46
45 cls = Permission
47 cls = Permission
46 global_perms = {
48 global_perms = {
@@ -122,8 +124,8 b' class PermissionModel(BaseModel):'
122 ('hg.repogroup.create.true', _('Enabled'))]
124 ('hg.repogroup.create.true', _('Enabled'))]
123
125
124 c_obj.fork_choices = [
126 c_obj.fork_choices = [
125 ('hg.fork.none', _('Disabled')),
127 (self.FORKING_DISABLED, _('Disabled')),
126 ('hg.fork.repository', _('Enabled'))]
128 (self.FORKING_ENABLED, _('Enabled'))]
127
129
128 c_obj.inherit_default_permission_choices = [
130 c_obj.inherit_default_permission_choices = [
129 ('hg.inherit_default_perms.false', _('Disabled')),
131 ('hg.inherit_default_perms.false', _('Disabled')),
@@ -908,7 +908,8 b' class PullRequestModel(BaseModel):'
908
908
909 try:
909 try:
910 if source_ref_type in self.REF_TYPES:
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 else:
913 else:
913 source_commit = source_repo.get_commit(source_ref_id)
914 source_commit = source_repo.get_commit(source_ref_id)
914 except CommitDoesNotExistError:
915 except CommitDoesNotExistError:
@@ -922,7 +923,8 b' class PullRequestModel(BaseModel):'
922
923
923 try:
924 try:
924 if target_ref_type in self.REF_TYPES:
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 else:
928 else:
927 target_commit = target_repo.get_commit(target_ref_id)
929 target_commit = target_repo.get_commit(target_ref_id)
928 except CommitDoesNotExistError:
930 except CommitDoesNotExistError:
@@ -46,6 +46,7 b' from rhodecode.model.db import ('
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
50
51
51 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
@@ -422,8 +423,15 b' class RepoModel(BaseModel):'
422 try:
423 try:
423 cur_repo = self._get_repo(repo)
424 cur_repo = self._get_repo(repo)
424 source_repo_name = cur_repo.repo_name
425 source_repo_name = cur_repo.repo_name
426
427 affected_user_ids = []
425 if 'user' in kwargs:
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 if 'repo_group' in kwargs:
436 if 'repo_group' in kwargs:
429 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
437 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
@@ -474,6 +482,9 b' class RepoModel(BaseModel):'
474 self._rename_filesystem_repo(
482 self._rename_filesystem_repo(
475 old=source_repo_name, new=new_name)
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 return cur_repo
488 return cur_repo
478 except Exception:
489 except Exception:
479 log.error(traceback.format_exc())
490 log.error(traceback.format_exc())
@@ -39,6 +39,7 b' from rhodecode.model import BaseModel'
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
41 UserGroup, Repository)
41 UserGroup, Repository)
42 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 from rhodecode.lib.caching_query import FromCache
44 from rhodecode.lib.caching_query import FromCache
44 from rhodecode.lib.utils2 import action_logger_generic
45 from rhodecode.lib.utils2 import action_logger_generic
@@ -531,8 +532,14 b' class RepoGroupModel(BaseModel):'
531
532
532 new_path = repo_group.full_path
533 new_path = repo_group.full_path
533
534
535 affected_user_ids = []
534 if 'user' in form_data:
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 self.sa.add(repo_group)
544 self.sa.add(repo_group)
538
545
@@ -566,6 +573,9 b' class RepoGroupModel(BaseModel):'
566 # Trigger update event.
573 # Trigger update event.
567 events.trigger(events.RepoGroupUpdateEvent(repo_group))
574 events.trigger(events.RepoGroupUpdateEvent(repo_group))
568
575
576 if affected_user_ids:
577 PermissionModel().trigger_permission_flush(affected_user_ids)
578
569 return repo_group
579 return repo_group
570 except Exception:
580 except Exception:
571 log.error(traceback.format_exc())
581 log.error(traceback.format_exc())
@@ -12,6 +12,12 b''
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
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 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
16 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
17 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
23 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
@@ -1331,7 +1331,7 b' var CommentsController = function() {'
1331
1331
1332 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1332 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1333 if ($comments.length===0) {
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 var $reply_container = $('#cb-comments-inline-container-template')
1335 var $reply_container = $('#cb-comments-inline-container-template')
1336 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1336 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1337 $td.append($($reply_container).html());
1337 $td.append($($reply_container).html());
@@ -115,6 +115,7 b''
115 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
115 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
116 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
116 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
117 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
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 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
119 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
119 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
120 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
120 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
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 <div class="radios">
62 <div class="radios">
63 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
63 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
64 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
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 </div>
66 </div>
67 </div>
67 </div>
68 <div class="field">
68 <div class="field">
@@ -24,7 +24,7 b''
24 ## to speed up lookups cache some functions before the loop
24 ## to speed up lookups cache some functions before the loop
25 <%
25 <%
26 active_patterns = h.get_active_pattern_entries(c.repo_name)
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 %for commit in c.commit_ranges:
30 %for commit in c.commit_ranges:
@@ -57,7 +57,7 b''
57 </td>
57 </td>
58 <td class="mid td-description">
58 <td class="mid td-description">
59 <div class="log-container truncate-wrap">
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 </div>
61 </div>
62 </td>
62 </td>
63 </tr>
63 </tr>
@@ -416,6 +416,12 b''
416 </a>
416 </a>
417 </%def>
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 <%def name="repo_artifact_uid(repo_name, file_uid)">
425 <%def name="repo_artifact_uid(repo_name, file_uid)">
420 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
426 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
421 </%def>
427 </%def>
@@ -443,6 +449,7 b''
443 % endif
449 % endif
444 </%def>
450 </%def>
445
451
452
446 <%def name="markup_form(form_id, form_text='', help_text=None)">
453 <%def name="markup_form(form_id, form_text='', help_text=None)">
447
454
448 <div class="markup-form">
455 <div class="markup-form">
@@ -221,7 +221,7 b' if (show_disabled) {'
221 <%= version_info %>
221 <%= version_info %>
222 <% } %>
222 <% } %>
223 <br/>
223 <br/>
224 File: <code><%- file_name -%></code>
224 File: <code><%= file_name -%></code>
225 <% } else { %>
225 <% } else { %>
226 <% if (review_status) { %>
226 <% if (review_status) { %>
227 <i class="icon-circle review-status-<%= review_status %>"></i>
227 <i class="icon-circle review-status-<%= review_status %>"></i>
@@ -27,8 +27,8 b''
27 <%def name="main()">
27 <%def name="main()">
28 ## Container to gather extracted Tickets
28 ## Container to gather extracted Tickets
29 <%
29 <%
30 c.referenced_commit_issues = []
30 c.referenced_commit_issues = h.IssuesRegistry()
31 c.referenced_desc_issues = []
31 c.referenced_desc_issues = h.IssuesRegistry()
32 %>
32 %>
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
@@ -86,7 +86,7 b''
86 </div>
86 </div>
87
87
88 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
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 </div>
90 </div>
91
91
92 <div id="pr-desc-edit" class="input textarea" style="display: none;">
92 <div id="pr-desc-edit" class="input textarea" style="display: none;">
@@ -435,7 +435,7 b''
435 </td>
435 </td>
436 <td class="mid td-description">
436 <td class="mid td-description">
437 <div class="log-container truncate-wrap">
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 </div>
439 </div>
440 </td>
440 </td>
441 </tr>
441 </tr>
@@ -809,7 +809,7 b''
809 <div class="sidebar-element clear-both">
809 <div class="sidebar-element clear-both">
810 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
810 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
811 <i class="icon-info-circled"></i>
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 </div>
813 </div>
814
814
815 <div class="right-sidebar-expanded-state pr-details-title">
815 <div class="right-sidebar-expanded-state pr-details-title">
@@ -822,15 +822,17 b''
822 <table>
822 <table>
823
823
824 <tr><td><code>${_('In pull request description')}:</code></td></tr>
824 <tr><td><code>${_('In pull request description')}:</code></td></tr>
825 % if c.referenced_desc_issues:
825 % if c.referenced_desc_issues.issues:
826 % for ticket_dict in sorted(c.referenced_desc_issues):
826
827 % for ticket_id, ticket_dict in c.referenced_desc_issues.unique_issues.items():
827 <tr>
828 <tr>
828 <td>
829 <td>
829 <a href="${ticket_dict.get('url')}">
830 <a href="${ticket_dict[0].get('url')}">
830 ${ticket_dict.get('id')}
831 ${ticket_id}
831 </a>
832 </a>
832 </td>
833 </td>
833 </tr>
834 </tr>
835
834 % endfor
836 % endfor
835 % else:
837 % else:
836 <tr>
838 <tr>
@@ -841,13 +843,14 b''
841 % endif
843 % endif
842
844
843 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
845 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
844 % if c.referenced_commit_issues:
846 % if c.referenced_commit_issues.issues:
845 % for ticket_dict in sorted(c.referenced_commit_issues):
847 % for ticket_id, ticket_dict in c.referenced_commit_issues.unique_issues.items():
846 <tr>
848 <tr>
847 <td>
849 <td>
848 <a href="${ticket_dict.get('url')}">
850 <a href="${ticket_dict[0].get('url')}">
849 ${ticket_dict.get('id')}
851 ${ticket_id}
850 </a>
852 </a>
853 - ${_ungettext('in %s commit', 'in %s commits', len(ticket_dict)) % (len(ticket_dict))}
851 </td>
854 </td>
852 </tr>
855 </tr>
853 % endfor
856 % endfor
@@ -95,7 +95,8 b''
95 var fname = selectedReference.raw_id + ext;
95 var fname = selectedReference.raw_id + ext;
96 var href = pyroutes.url('repo_archivefile', {
96 var href = pyroutes.url('repo_archivefile', {
97 'repo_name': templateContext.repo_name,
97 'repo_name': templateContext.repo_name,
98 'fname': fname
98 'fname': fname,
99 'with_hash': '1'
99 });
100 });
100 // set new label
101 // set new label
101 $(this).html(ico + ' {0}{1}'.format(escapeHtml(e.added.text), ext));
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