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 | [bumpversion] |
|
1 | [bumpversion] | |
2 |
current_version = 4.2 |
|
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 = |
|
20 | state = in_progress | |
24 |
version = 4.2 |
|
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.2 |
|
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; |
@@ -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( |
|
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 |
|
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', |
|
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', |
|
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 |
|
|
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( |
|
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 |
( |
|
127 | (self.FORKING_DISABLED, _('Disabled')), | |
126 |
( |
|
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( |
|
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( |
|
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 r |
|
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 |
|
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><% |
|
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 |
${ |
|
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_ |
|
831 | ${ticket_id} | |
831 | </a> |
|
832 | </a> | |
832 | </td> |
|
833 | </td> | |
833 | </tr> |
|
834 | </tr> | |
|
835 | ||||
834 |
|
|
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 |
|
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_ |
|
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
You need to be logged in to leave comments.
Login now