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