Auto status change to "Under Review"
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.2 |
|
|
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 = |
|
|
24 |
version = 4.2 |
|
|
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.2 |
|
|
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; |
@@ -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( |
|
|
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 |
|
|
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', |
|
|
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', |
|
|
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 |
|
|
|
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,20 +240,33 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,): |
|
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: | |
|
256 | 270 |
|
|
257 | 271 | |
|
258 | 272 | # Ensure we return full id |
@@ -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( |
|
|
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 |
( |
|
|
126 |
( |
|
|
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( |
|
|
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( |
|
|
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 r |
|
|
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 |
|
|
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><% |
|
|
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 |
${ |
|
|
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_ |
|
|
830 | <a href="${ticket_dict[0].get('url')}"> | |
|
831 | ${ticket_id} | |
|
831 | 832 | </a> |
|
832 | 833 | </td> |
|
833 | 834 | </tr> |
|
835 | ||
|
834 | 836 |
|
|
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 |
|
|
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_ |
|
|
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
You need to be logged in to leave comments.
Login now