release: Merge default into stable for release preparation
This commit is contained in:
commit
5aa8df7b67
65 changed files with 8505 additions and 1103 deletions
|
|
@ -1,6 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 4.20.1
|
||||
current_version = 4.21.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.20.1
|
||||
|
||||
[task:updated_translation]
|
||||
|
||||
[release]
|
||||
state = in_progress
|
||||
version = 4.21.0
|
||||
|
||||
[task:generate_js_routes]
|
||||
|
||||
[task:updated_trial_license]
|
||||
|
|
|
|||
|
|
@ -147,12 +147,13 @@ Use the following example to configure Nginx as a your web server.
|
|||
|
||||
## Special Cache for file store, make sure you enable this intentionally as
|
||||
## it could bypass upload files permissions
|
||||
# location /_file_store/download {
|
||||
# location /_file_store/download/gravatars {
|
||||
#
|
||||
# proxy_cache cache_zone;
|
||||
# # ignore Set-Cookie
|
||||
# proxy_ignore_headers Set-Cookie;
|
||||
# proxy_ignore_headers Cookie;
|
||||
# # ignore cache-control
|
||||
# proxy_ignore_headers Cache-Control;
|
||||
#
|
||||
# proxy_cache_key $host$uri$is_args$args;
|
||||
# proxy_cache_methods GET;
|
||||
|
|
|
|||
18
pkgs/patches/beaker/patch-beaker-improved-redis-2.diff
Normal file
18
pkgs/patches/beaker/patch-beaker-improved-redis-2.diff
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
diff -rup Beaker-1.9.1-orig/beaker/session.py Beaker-1.9.1/beaker/session.py
|
||||
--- Beaker-1.9.1-orig/beaker/session.py 2020-04-10 10:23:04.000000000 +0200
|
||||
+++ Beaker-1.9.1/beaker/session.py 2020-04-10 10:23:34.000000000 +0200
|
||||
@@ -156,6 +156,14 @@ def __init__(self, request, id=None, invalidate_corrupt=False,
|
||||
if timeout and not save_accessed_time:
|
||||
raise BeakerException("timeout requires save_accessed_time")
|
||||
self.timeout = timeout
|
||||
+ # We want to pass timeout param to redis backend to support expiration of keys
|
||||
+ # In future, I believe, we can use this param for memcached and mongo as well
|
||||
+ if self.timeout is not None and self.type == 'ext:redis':
|
||||
+ # The backend expiration should always be a bit longer (I decied to use 2 minutes) than the
|
||||
+ # session expiration itself to prevent the case where the backend data expires while
|
||||
+ # the session is being read (PR#153)
|
||||
+ self.namespace_args['timeout'] = self.timeout + 60 * 2
|
||||
+
|
||||
self.save_atime = save_accessed_time
|
||||
self.use_cookies = use_cookies
|
||||
self.cookie_expires = cookie_expires
|
||||
26
pkgs/patches/beaker/patch-beaker-improved-redis.diff
Normal file
26
pkgs/patches/beaker/patch-beaker-improved-redis.diff
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
diff -rup Beaker-1.9.1-orig/beaker/ext/redisnm.py Beaker-1.9.1/beaker/ext/redisnm.py
|
||||
--- Beaker-1.9.1-orig/beaker/ext/redisnm.py 2018-04-10 10:23:04.000000000 +0200
|
||||
+++ Beaker-1.9.1/beaker/ext/redisnm.py 2018-04-10 10:23:34.000000000 +0200
|
||||
@@ -30,9 +30,10 @@ class RedisNamespaceManager(NamespaceManager):
|
||||
|
||||
clients = SyncDict()
|
||||
|
||||
- def __init__(self, namespace, url, **kw):
|
||||
+ def __init__(self, namespace, url, timeout=None, **kw):
|
||||
super(RedisNamespaceManager, self).__init__(namespace)
|
||||
self.lock_dir = None # Redis uses redis itself for locking.
|
||||
+ self.timeout = timeout
|
||||
|
||||
if redis is None:
|
||||
raise RuntimeError('redis is not available')
|
||||
@@ -68,6 +69,8 @@ def has_key(self, key):
|
||||
|
||||
def set_value(self, key, value, expiretime=None):
|
||||
value = pickle.dumps(value)
|
||||
+ if expiretime is None and self.timeout is not None:
|
||||
+ expiretime = self.timeout
|
||||
if expiretime is not None:
|
||||
self.client.setex(self._format_key(key), int(expiretime), value)
|
||||
else:
|
||||
|
||||
|
||||
|
|
@ -32,6 +32,8 @@ self: super: {
|
|||
patches = [
|
||||
./patches/beaker/patch-beaker-lock-func-debug.diff
|
||||
./patches/beaker/patch-beaker-metadata-reuse.diff
|
||||
./patches/beaker/patch-beaker-improved-redis.diff
|
||||
./patches/beaker/patch-beaker-improved-redis-2.diff
|
||||
];
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,20 @@ self: super: {
|
|||
license = [ pkgs.lib.licenses.bsdOriginal ];
|
||||
};
|
||||
};
|
||||
"apispec" = super.buildPythonPackage {
|
||||
name = "apispec-1.0.0";
|
||||
doCheck = false;
|
||||
propagatedBuildInputs = [
|
||||
self."PyYAML"
|
||||
];
|
||||
src = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/67/15/346c04988dd67d36007e28145504c520491930c878b1f484a97b27a8f497/apispec-1.0.0.tar.gz";
|
||||
sha256 = "1712w1anvqrvadjjpvai84vbaygaxabd3zz5lxihdzwzs4gvi9sp";
|
||||
};
|
||||
meta = {
|
||||
license = [ pkgs.lib.licenses.mit ];
|
||||
};
|
||||
};
|
||||
"appenlight-client" = super.buildPythonPackage {
|
||||
name = "appenlight-client-0.6.26";
|
||||
doCheck = false;
|
||||
|
|
@ -236,20 +250,23 @@ self: super: {
|
|||
};
|
||||
};
|
||||
"channelstream" = super.buildPythonPackage {
|
||||
name = "channelstream-0.5.2";
|
||||
name = "channelstream-0.6.14";
|
||||
doCheck = false;
|
||||
propagatedBuildInputs = [
|
||||
self."gevent"
|
||||
self."ws4py"
|
||||
self."marshmallow"
|
||||
self."python-dateutil"
|
||||
self."pyramid"
|
||||
self."pyramid-jinja2"
|
||||
self."pyramid-apispec"
|
||||
self."itsdangerous"
|
||||
self."requests"
|
||||
self."six"
|
||||
];
|
||||
src = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
|
||||
sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
|
||||
url = "https://files.pythonhosted.org/packages/d4/2d/86d6757ccd06ce673ee224123471da3d45251d061da7c580bfc259bad853/channelstream-0.6.14.tar.gz";
|
||||
sha256 = "0qgy5j3rj6c8cslzidh32glhkrhbbdxjc008y69v8a0y3zyaz2d3";
|
||||
};
|
||||
meta = {
|
||||
license = [ pkgs.lib.licenses.bsdOriginal ];
|
||||
|
|
@ -862,11 +879,11 @@ self: super: {
|
|||
};
|
||||
};
|
||||
"itsdangerous" = super.buildPythonPackage {
|
||||
name = "itsdangerous-0.24";
|
||||
name = "itsdangerous-1.1.0";
|
||||
doCheck = false;
|
||||
src = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
|
||||
sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
|
||||
url = "https://files.pythonhosted.org/packages/68/1a/f27de07a8a304ad5fa817bbe383d1238ac4396da447fa11ed937039fa04b/itsdangerous-1.1.0.tar.gz";
|
||||
sha256 = "068zpbksq5q2z4dckh2k1zbcq43ay74ylqn77rni797j0wyh66rj";
|
||||
};
|
||||
meta = {
|
||||
license = [ pkgs.lib.licenses.bsdOriginal ];
|
||||
|
|
@ -993,6 +1010,17 @@ self: super: {
|
|||
license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
|
||||
};
|
||||
};
|
||||
"marshmallow" = super.buildPythonPackage {
|
||||
name = "marshmallow-2.18.0";
|
||||
doCheck = false;
|
||||
src = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/ad/0b/5799965d1c6d5f608d684e2c0dce8a828e0309a3bfe8327d9418a89f591c/marshmallow-2.18.0.tar.gz";
|
||||
sha256 = "1g0aafpjn7yaxq06yndy8c7rs9n42adxkqq1ayhlr869pr06d3lm";
|
||||
};
|
||||
meta = {
|
||||
license = [ pkgs.lib.licenses.mit ];
|
||||
};
|
||||
};
|
||||
"mistune" = super.buildPythonPackage {
|
||||
name = "mistune-0.8.4";
|
||||
doCheck = false;
|
||||
|
|
@ -1522,6 +1550,20 @@ self: super: {
|
|||
license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
|
||||
};
|
||||
};
|
||||
"pyramid-apispec" = super.buildPythonPackage {
|
||||
name = "pyramid-apispec-0.3.2";
|
||||
doCheck = false;
|
||||
propagatedBuildInputs = [
|
||||
self."apispec"
|
||||
];
|
||||
src = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/2a/30/1dea5d81ea635449572ba60ec3148310d75ae4530c3c695f54b0991bb8c7/pyramid_apispec-0.3.2.tar.gz";
|
||||
sha256 = "0ffrcqp9dkykivhfcq0v9lgy6w0qhwl6x78925vfjmayly9r8da0";
|
||||
};
|
||||
meta = {
|
||||
license = [ pkgs.lib.licenses.bsdOriginal ];
|
||||
};
|
||||
};
|
||||
"pyramid-mailer" = super.buildPythonPackage {
|
||||
name = "pyramid-mailer-0.15.1";
|
||||
doCheck = false;
|
||||
|
|
@ -1763,6 +1805,17 @@ self: super: {
|
|||
license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
|
||||
};
|
||||
};
|
||||
"PyYAML" = super.buildPythonPackage {
|
||||
name = "PyYAML-5.3.1";
|
||||
doCheck = false;
|
||||
src = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/64/c2/b80047c7ac2478f9501676c988a5411ed5572f35d1beff9cae07d321512c/PyYAML-5.3.1.tar.gz";
|
||||
sha256 = "0pb4zvkfxfijkpgd1b86xjsqql97ssf1knbd1v53wkg1qm9cgsmq";
|
||||
};
|
||||
meta = {
|
||||
license = [ pkgs.lib.licenses.mit ];
|
||||
};
|
||||
};
|
||||
"redis" = super.buildPythonPackage {
|
||||
name = "redis-3.4.1";
|
||||
doCheck = false;
|
||||
|
|
@ -1819,7 +1872,7 @@ self: super: {
|
|||
};
|
||||
};
|
||||
"rhodecode-enterprise-ce" = super.buildPythonPackage {
|
||||
name = "rhodecode-enterprise-ce-4.20.1";
|
||||
name = "rhodecode-enterprise-ce-4.20.0";
|
||||
buildInputs = [
|
||||
self."pytest"
|
||||
self."py"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ babel==1.3
|
|||
beaker==1.9.1
|
||||
bleach==3.1.3
|
||||
celery==4.3.0
|
||||
channelstream==0.5.2
|
||||
channelstream==0.6.14
|
||||
click==7.0
|
||||
colander==1.7.0
|
||||
# our custom configobj
|
||||
|
|
@ -22,7 +22,7 @@ future==0.14.3
|
|||
futures==3.0.2
|
||||
infrae.cache==1.0.1
|
||||
iso8601==0.1.12
|
||||
itsdangerous==0.24
|
||||
itsdangerous==1.1.0
|
||||
kombu==4.6.6
|
||||
lxml==4.2.5
|
||||
mako==1.1.0
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ jsonschema==2.6.0
|
|||
pluggy==0.13.1
|
||||
pyasn1-modules==0.2.6
|
||||
pyramid-jinja2==2.7
|
||||
pyramid-apispec==0.3.2
|
||||
scandir==1.10.0
|
||||
setproctitle==1.1.10
|
||||
tempita==0.5.2
|
||||
testpath==0.4.4
|
||||
transaction==2.4.0
|
||||
vine==1.3.0
|
||||
wcwidth==0.1.9
|
||||
wcwidth==0.1.9
|
||||
|
|
@ -1 +1 @@
|
|||
4.20.1
|
||||
4.21.0
|
||||
|
|
@ -48,7 +48,7 @@ PYRAMID_SETTINGS = {}
|
|||
EXTENSIONS = {}
|
||||
|
||||
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
|
||||
__dbversion__ = 108 # defines current db version for migrations
|
||||
__dbversion__ = 109 # defines current db version for migrations
|
||||
__platform__ = platform.system()
|
||||
__license__ = 'AGPLv3, and Commercial License'
|
||||
__author__ = 'RhodeCode GmbH'
|
||||
|
|
|
|||
|
|
@ -170,8 +170,7 @@ def validate_repo_permissions(apiuser, repoid, repo, perms):
|
|||
"""
|
||||
if not HasRepoPermissionAnyApi(*perms)(
|
||||
user=apiuser, repo_name=repo.repo_name):
|
||||
raise JSONRPCError(
|
||||
'repository `%s` does not exist' % repoid)
|
||||
raise JSONRPCError('repository `%s` does not exist' % repoid)
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -307,8 +307,7 @@ def get_repo_changeset(request, apiuser, repoid, revision,
|
|||
"""
|
||||
repo = get_repo_or_error(repoid)
|
||||
if not has_superadmin_permission(apiuser):
|
||||
_perms = (
|
||||
'repository.admin', 'repository.write', 'repository.read',)
|
||||
_perms = ('repository.admin', 'repository.write', 'repository.read',)
|
||||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
|
||||
changes_details = Optional.extract(details)
|
||||
|
|
@ -366,8 +365,7 @@ def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
|
|||
"""
|
||||
repo = get_repo_or_error(repoid)
|
||||
if not has_superadmin_permission(apiuser):
|
||||
_perms = (
|
||||
'repository.admin', 'repository.write', 'repository.read',)
|
||||
_perms = ('repository.admin', 'repository.write', 'repository.read',)
|
||||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
|
||||
changes_details = Optional.extract(details)
|
||||
|
|
@ -1021,7 +1019,8 @@ def update_repo(
|
|||
|
||||
include_secrets = False
|
||||
if not has_superadmin_permission(apiuser):
|
||||
validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
|
||||
_perms = ('repository.admin',)
|
||||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
else:
|
||||
include_secrets = True
|
||||
|
||||
|
|
@ -1208,8 +1207,7 @@ def fork_repo(request, apiuser, repoid, fork_name,
|
|||
if not has_superadmin_permission(apiuser):
|
||||
# check if we have at least read permission for
|
||||
# this repo that we fork !
|
||||
_perms = (
|
||||
'repository.admin', 'repository.write', 'repository.read')
|
||||
_perms = ('repository.admin', 'repository.write', 'repository.read')
|
||||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
|
||||
# check if the regular user has at least fork permissions as well
|
||||
|
|
@ -2370,12 +2368,13 @@ def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
|
|||
}
|
||||
"""
|
||||
|
||||
# Restrict access to this api method to admins only.
|
||||
# Restrict access to this api method to super-admins, and repo admins only.
|
||||
repo = get_repo_or_error(repoid)
|
||||
if not has_superadmin_permission(apiuser):
|
||||
raise JSONRPCForbidden()
|
||||
_perms = ('repository.admin',)
|
||||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
|
||||
try:
|
||||
repo = get_repo_or_error(repoid)
|
||||
settings_model = VcsSettingsModel(repo=repo)
|
||||
settings = settings_model.get_global_settings()
|
||||
settings.update(settings_model.get_repo_settings())
|
||||
|
|
@ -2414,9 +2413,11 @@ def set_repo_settings(request, apiuser, repoid, settings):
|
|||
"result": true
|
||||
}
|
||||
"""
|
||||
# Restrict access to this api method to admins only.
|
||||
# Restrict access to this api method to super-admins, and repo admins only.
|
||||
repo = get_repo_or_error(repoid)
|
||||
if not has_superadmin_permission(apiuser):
|
||||
raise JSONRPCForbidden()
|
||||
_perms = ('repository.admin',)
|
||||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
|
||||
if type(settings) is not dict:
|
||||
raise JSONRPCError('Settings have to be a JSON Object.')
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ from rhodecode.lib.channelstream import (
|
|||
get_user_data,
|
||||
parse_channels_info,
|
||||
update_history_from_logs,
|
||||
STATE_PUBLIC_KEYS)
|
||||
USER_STATE_PUBLIC_KEYS)
|
||||
|
||||
from rhodecode.lib.auth import NotAnonymous
|
||||
|
||||
|
|
@ -86,14 +86,16 @@ class ChannelstreamView(BaseAppView):
|
|||
'display_name': None,
|
||||
'display_link': None,
|
||||
}
|
||||
user_data['permissions'] = self._rhodecode_user.permissions_safe
|
||||
|
||||
#user_data['permissions'] = self._rhodecode_user.permissions_safe
|
||||
|
||||
payload = {
|
||||
'username': user.username,
|
||||
'user_state': user_data,
|
||||
'conn_id': str(uuid.uuid4()),
|
||||
'channels': channels,
|
||||
'channel_configs': {},
|
||||
'state_public_keys': STATE_PUBLIC_KEYS,
|
||||
'state_public_keys': USER_STATE_PUBLIC_KEYS,
|
||||
'info': {
|
||||
'exclude_channels': ['broadcast']
|
||||
}
|
||||
|
|
@ -118,10 +120,13 @@ class ChannelstreamView(BaseAppView):
|
|||
'Channelstream service at {} is down'.format(channelstream_url))
|
||||
return HTTPBadGateway()
|
||||
|
||||
channel_info = connect_result.get('channels_info')
|
||||
if not channel_info:
|
||||
raise HTTPBadRequest()
|
||||
|
||||
connect_result['channels'] = channels
|
||||
connect_result['channels_info'] = parse_channels_info(
|
||||
connect_result['channels_info'],
|
||||
include_channel_info=filtered_channels)
|
||||
channel_info, include_channel_info=filtered_channels)
|
||||
update_history_from_logs(self.channelstream_config,
|
||||
filtered_channels, connect_result)
|
||||
return connect_result
|
||||
|
|
@ -167,10 +172,15 @@ class ChannelstreamView(BaseAppView):
|
|||
log.exception(
|
||||
'Channelstream service at {} is down'.format(channelstream_url))
|
||||
return HTTPBadGateway()
|
||||
|
||||
channel_info = connect_result.get('channels_info')
|
||||
if not channel_info:
|
||||
raise HTTPBadRequest()
|
||||
|
||||
# include_channel_info will limit history only to new channel
|
||||
# to not overwrite histories on other channels in client
|
||||
connect_result['channels_info'] = parse_channels_info(
|
||||
connect_result['channels_info'],
|
||||
channel_info,
|
||||
include_channel_info=filtered_channels)
|
||||
update_history_from_logs(
|
||||
self.channelstream_config, filtered_channels, connect_result)
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ def includeme(config):
|
|||
pattern='/_file_store/upload')
|
||||
config.add_route(
|
||||
name='download_file',
|
||||
pattern='/_file_store/download/{fid}')
|
||||
pattern='/_file_store/download/{fid:.*}')
|
||||
config.add_route(
|
||||
name='download_file_by_token',
|
||||
pattern='/_file_store/token-download/{_auth_token}/{fid}')
|
||||
pattern='/_file_store/token-download/{_auth_token}/{fid:.*}')
|
||||
|
||||
# Scan module for configuration decorators.
|
||||
config.scan('.views', ignore='.tests')
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
import os
|
||||
import time
|
||||
import errno
|
||||
import shutil
|
||||
import hashlib
|
||||
|
||||
|
|
@ -32,8 +33,23 @@ from rhodecode.apps.file_store.exceptions import (
|
|||
METADATA_VER = 'v1'
|
||||
|
||||
|
||||
def safe_make_dirs(dir_path):
|
||||
if not os.path.exists(dir_path):
|
||||
try:
|
||||
os.makedirs(dir_path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
return
|
||||
|
||||
|
||||
class LocalFileStorage(object):
|
||||
|
||||
@classmethod
|
||||
def apply_counter(cls, counter, filename):
|
||||
name_counted = '%d-%s' % (counter, filename)
|
||||
return name_counted
|
||||
|
||||
@classmethod
|
||||
def resolve_name(cls, name, directory):
|
||||
"""
|
||||
|
|
@ -47,17 +63,16 @@ class LocalFileStorage(object):
|
|||
|
||||
counter = 0
|
||||
while True:
|
||||
name = '%d-%s' % (counter, name)
|
||||
name_counted = cls.apply_counter(counter, name)
|
||||
|
||||
# sub_store prefix to optimize disk usage, e.g some_path/ab/final_file
|
||||
sub_store = cls._sub_store_from_filename(name)
|
||||
sub_store = cls._sub_store_from_filename(name_counted)
|
||||
sub_store_path = os.path.join(directory, sub_store)
|
||||
if not os.path.exists(sub_store_path):
|
||||
os.makedirs(sub_store_path)
|
||||
safe_make_dirs(sub_store_path)
|
||||
|
||||
path = os.path.join(sub_store_path, name)
|
||||
path = os.path.join(sub_store_path, name_counted)
|
||||
if not os.path.exists(path):
|
||||
return name, path
|
||||
return name_counted, path
|
||||
counter += 1
|
||||
|
||||
@classmethod
|
||||
|
|
@ -102,8 +117,13 @@ class LocalFileStorage(object):
|
|||
|
||||
:param filename: base name of file
|
||||
"""
|
||||
sub_store = self._sub_store_from_filename(filename)
|
||||
return os.path.join(self.base_path, sub_store, filename)
|
||||
prefix_dir = ''
|
||||
if '/' in filename:
|
||||
prefix_dir, filename = filename.split('/')
|
||||
sub_store = self._sub_store_from_filename(filename)
|
||||
else:
|
||||
sub_store = self._sub_store_from_filename(filename)
|
||||
return os.path.join(self.base_path, prefix_dir, sub_store, filename)
|
||||
|
||||
def delete(self, filename):
|
||||
"""
|
||||
|
|
@ -123,7 +143,7 @@ class LocalFileStorage(object):
|
|||
Checks if file exists. Resolves filename's absolute
|
||||
path based on base_path.
|
||||
|
||||
:param filename: base name of file
|
||||
:param filename: file_uid name of file, e.g 0-f62b2b2d-9708-4079-a071-ec3f958448d4.svg
|
||||
"""
|
||||
return os.path.exists(self.store_path(filename))
|
||||
|
||||
|
|
@ -158,7 +178,7 @@ class LocalFileStorage(object):
|
|||
return ext in [normalize_ext(x) for x in extensions]
|
||||
|
||||
def save_file(self, file_obj, filename, directory=None, extensions=None,
|
||||
extra_metadata=None, max_filesize=None, **kwargs):
|
||||
extra_metadata=None, max_filesize=None, randomized_name=True, **kwargs):
|
||||
"""
|
||||
Saves a file object to the uploads location.
|
||||
Returns the resolved filename, i.e. the directory +
|
||||
|
|
@ -169,6 +189,7 @@ class LocalFileStorage(object):
|
|||
:param directory: relative path of sub-directory
|
||||
:param extensions: iterable of allowed extensions, if not default
|
||||
:param max_filesize: maximum size of file that should be allowed
|
||||
:param randomized_name: generate random generated UID or fixed based on the filename
|
||||
:param extra_metadata: extra JSON metadata to store next to the file with .meta suffix
|
||||
|
||||
"""
|
||||
|
|
@ -183,13 +204,12 @@ class LocalFileStorage(object):
|
|||
else:
|
||||
dest_directory = self.base_path
|
||||
|
||||
if not os.path.exists(dest_directory):
|
||||
os.makedirs(dest_directory)
|
||||
safe_make_dirs(dest_directory)
|
||||
|
||||
filename = utils.uid_filename(filename)
|
||||
uid_filename = utils.uid_filename(filename, randomized=randomized_name)
|
||||
|
||||
# resolve also produces special sub-dir for file optimized store
|
||||
filename, path = self.resolve_name(filename, dest_directory)
|
||||
filename, path = self.resolve_name(uid_filename, dest_directory)
|
||||
stored_file_dir = os.path.dirname(path)
|
||||
|
||||
file_obj.seek(0)
|
||||
|
|
@ -210,12 +230,13 @@ class LocalFileStorage(object):
|
|||
|
||||
file_hash = self.calculate_path_hash(path)
|
||||
|
||||
metadata.update(
|
||||
{"filename": filename,
|
||||
metadata.update({
|
||||
"filename": filename,
|
||||
"size": size,
|
||||
"time": time.time(),
|
||||
"sha256": file_hash,
|
||||
"meta_ver": METADATA_VER})
|
||||
"meta_ver": METADATA_VER
|
||||
})
|
||||
|
||||
filename_meta = filename + '.meta'
|
||||
with open(os.path.join(stored_file_dir, filename_meta), "wb") as dest_meta:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
|
||||
import uuid
|
||||
|
||||
import StringIO
|
||||
import pathlib2
|
||||
|
||||
|
||||
|
|
@ -52,3 +52,7 @@ def uid_filename(filename, randomized=True):
|
|||
hash_key = '{}.{}'.format(filename, 'store')
|
||||
uid = uuid.uuid5(uuid.NAMESPACE_URL, hash_key)
|
||||
return str(uid) + ext.lower()
|
||||
|
||||
|
||||
def bytes_to_file_obj(bytes_data):
|
||||
return StringIO.StringIO(bytes_data)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class FileStoreView(BaseAppView):
|
|||
file_uid, store_path)
|
||||
raise HTTPNotFound()
|
||||
|
||||
db_obj = FileStore().query().filter(FileStore.file_uid == file_uid).scalar()
|
||||
db_obj = FileStore.get_by_store_uid(file_uid, safe=True)
|
||||
if not db_obj:
|
||||
raise HTTPNotFound()
|
||||
|
||||
|
|
|
|||
|
|
@ -345,6 +345,16 @@ def includeme(config):
|
|||
pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
|
||||
repo_route=True, repo_accepted_types=['hg', 'git'])
|
||||
|
||||
config.add_route(
|
||||
name='pullrequest_comments',
|
||||
pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments',
|
||||
repo_route=True)
|
||||
|
||||
config.add_route(
|
||||
name='pullrequest_todos',
|
||||
pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos',
|
||||
repo_route=True)
|
||||
|
||||
# Artifacts, (EE feature)
|
||||
config.add_route(
|
||||
name='repo_artifacts_list',
|
||||
|
|
|
|||
|
|
@ -485,23 +485,10 @@ class TestRepoCommitCommentsView(TestController):
|
|||
|
||||
|
||||
def assert_comment_links(response, comments, inline_comments):
|
||||
if comments == 1:
|
||||
comments_text = "%d General" % comments
|
||||
else:
|
||||
comments_text = "%d General" % comments
|
||||
response.mustcontain(
|
||||
'<span class="display-none" id="general-comments-count">{}</span>'.format(comments))
|
||||
response.mustcontain(
|
||||
'<span class="display-none" id="inline-comments-count">{}</span>'.format(inline_comments))
|
||||
|
||||
if inline_comments == 1:
|
||||
inline_comments_text = "%d Inline" % inline_comments
|
||||
else:
|
||||
inline_comments_text = "%d Inline" % inline_comments
|
||||
|
||||
if comments:
|
||||
response.mustcontain('<a href="#comments">%s</a>,' % comments_text)
|
||||
else:
|
||||
response.mustcontain(comments_text)
|
||||
|
||||
if inline_comments:
|
||||
response.mustcontain(
|
||||
'id="inline-comments-counter">%s' % inline_comments_text)
|
||||
else:
|
||||
response.mustcontain(inline_comments_text)
|
||||
|
|
|
|||
|
|
@ -619,7 +619,12 @@ class ComparePage(AssertResponse):
|
|||
self.contains_one_anchor(file_id)
|
||||
diffblock = doc.cssselect('[data-f-path="%s"]' % filename)
|
||||
assert len(diffblock) == 2
|
||||
assert len(diffblock[0].cssselect('a[href="#%s"]' % file_id)) == 1
|
||||
for lnk in diffblock[0].cssselect('a'):
|
||||
if 'permalink' in lnk.text:
|
||||
assert '#{}'.format(file_id) in lnk.attrib['href']
|
||||
break
|
||||
else:
|
||||
pytest.fail('Unable to find permalink')
|
||||
|
||||
def contains_change_summary(self, files_changed, inserted, deleted):
|
||||
template = (
|
||||
|
|
|
|||
|
|
@ -150,9 +150,9 @@ class TestPullrequestsView(object):
|
|||
response = self.app.post(
|
||||
route_path('pullrequest_create', repo_name=source.repo_name),
|
||||
[
|
||||
('source_repo', source.repo_name),
|
||||
('source_repo', source_repo_name),
|
||||
('source_ref', source_ref),
|
||||
('target_repo', target.repo_name),
|
||||
('target_repo', target_repo_name),
|
||||
('target_ref', target_ref),
|
||||
('common_ancestor', commit_ids['initial-commit']),
|
||||
('pullrequest_title', 'Title'),
|
||||
|
|
@ -1110,16 +1110,17 @@ class TestPullrequestsView(object):
|
|||
|
||||
# source has ancestor - change - change-2
|
||||
backend.pull_heads(source, heads=['change-2'])
|
||||
target_repo_name = target.repo_name
|
||||
|
||||
# update PR
|
||||
self.app.post(
|
||||
route_path('pullrequest_update',
|
||||
repo_name=target.repo_name, pull_request_id=pull_request_id),
|
||||
repo_name=target_repo_name, pull_request_id=pull_request_id),
|
||||
params={'update_commits': 'true', 'csrf_token': csrf_token})
|
||||
|
||||
response = self.app.get(
|
||||
route_path('pullrequest_show',
|
||||
repo_name=target.repo_name,
|
||||
repo_name=target_repo_name,
|
||||
pull_request_id=pull_request.pull_request_id))
|
||||
|
||||
assert response.status_int == 200
|
||||
|
|
@ -1166,10 +1167,11 @@ class TestPullrequestsView(object):
|
|||
# source has ancestor - ancestor-new - change-rebased
|
||||
backend.pull_heads(target, heads=['ancestor-new'])
|
||||
backend.pull_heads(source, heads=['change-rebased'])
|
||||
target_repo_name = target.repo_name
|
||||
|
||||
# update PR
|
||||
url = route_path('pullrequest_update',
|
||||
repo_name=target.repo_name,
|
||||
repo_name=target_repo_name,
|
||||
pull_request_id=pull_request_id)
|
||||
self.app.post(url,
|
||||
params={'update_commits': 'true', 'csrf_token': csrf_token},
|
||||
|
|
@ -1183,7 +1185,7 @@ class TestPullrequestsView(object):
|
|||
|
||||
response = self.app.get(
|
||||
route_path('pullrequest_show',
|
||||
repo_name=target.repo_name,
|
||||
repo_name=target_repo_name,
|
||||
pull_request_id=pull_request.pull_request_id))
|
||||
assert response.status_int == 200
|
||||
response.mustcontain('Pull request updated to')
|
||||
|
|
@ -1232,16 +1234,17 @@ class TestPullrequestsView(object):
|
|||
vcsrepo = target.scm_instance()
|
||||
vcsrepo.config.clear_section('hooks')
|
||||
vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
|
||||
target_repo_name = target.repo_name
|
||||
|
||||
# update PR
|
||||
url = route_path('pullrequest_update',
|
||||
repo_name=target.repo_name,
|
||||
repo_name=target_repo_name,
|
||||
pull_request_id=pull_request_id)
|
||||
self.app.post(url,
|
||||
params={'update_commits': 'true', 'csrf_token': csrf_token},
|
||||
status=200)
|
||||
|
||||
response = self.app.get(route_path('pullrequest_new', repo_name=target.repo_name))
|
||||
response = self.app.get(route_path('pullrequest_new', repo_name=target_repo_name))
|
||||
assert response.status_int == 200
|
||||
response.mustcontain('Pull request updated to')
|
||||
response.mustcontain('with 0 added, 0 removed commits.')
|
||||
|
|
@ -1280,11 +1283,12 @@ class TestPullrequestsView(object):
|
|||
# source has ancestor - ancestor-new - change-rebased
|
||||
backend.pull_heads(target, heads=['ancestor-new'])
|
||||
backend.pull_heads(source, heads=['change-rebased'])
|
||||
target_repo_name = target.repo_name
|
||||
|
||||
# update PR
|
||||
self.app.post(
|
||||
route_path('pullrequest_update',
|
||||
repo_name=target.repo_name, pull_request_id=pull_request_id),
|
||||
repo_name=target_repo_name, pull_request_id=pull_request_id),
|
||||
params={'update_commits': 'true', 'csrf_token': csrf_token},
|
||||
status=200)
|
||||
|
||||
|
|
@ -1389,6 +1393,8 @@ class TestPullrequestsView(object):
|
|||
pull_request = pr_util.create_pull_request(
|
||||
commits, target_head='old-feature', source_head='new-feature',
|
||||
revisions=['new-feature'], mergeable=True)
|
||||
pr_id = pull_request.pull_request_id
|
||||
target_repo_name = pull_request.target_repo.repo_name
|
||||
|
||||
vcs = pr_util.source_repository.scm_instance()
|
||||
if backend.alias == 'git':
|
||||
|
|
@ -1397,8 +1403,8 @@ class TestPullrequestsView(object):
|
|||
vcs.strip(pr_util.commit_ids['new-feature'])
|
||||
|
||||
url = route_path('pullrequest_update',
|
||||
repo_name=pull_request.target_repo.repo_name,
|
||||
pull_request_id=pull_request.pull_request_id)
|
||||
repo_name=target_repo_name,
|
||||
pull_request_id=pr_id)
|
||||
response = self.app.post(url,
|
||||
params={'update_commits': 'true',
|
||||
'csrf_token': csrf_token})
|
||||
|
|
@ -1409,8 +1415,8 @@ class TestPullrequestsView(object):
|
|||
# Make sure that after update, it won't raise 500 errors
|
||||
response = self.app.get(route_path(
|
||||
'pullrequest_show',
|
||||
repo_name=pr_util.target_repository.repo_name,
|
||||
pull_request_id=pull_request.pull_request_id))
|
||||
repo_name=target_repo_name,
|
||||
pull_request_id=pr_id))
|
||||
|
||||
assert response.status_int == 200
|
||||
response.assert_response().element_contains(
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@
|
|||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
import logging
|
||||
import collections
|
||||
|
||||
from pyramid.httpexceptions import (
|
||||
HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
|
||||
|
|
@ -34,14 +34,14 @@ from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOv
|
|||
from rhodecode.lib import diffs, codeblocks
|
||||
from rhodecode.lib.auth import (
|
||||
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
|
||||
|
||||
from rhodecode.lib.ext_json import json
|
||||
from rhodecode.lib.compat import OrderedDict
|
||||
from rhodecode.lib.diffs import (
|
||||
cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
|
||||
get_diff_whitespace_flag)
|
||||
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
|
||||
import rhodecode.lib.helpers as h
|
||||
from rhodecode.lib.utils2 import safe_unicode, str2bool
|
||||
from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
|
||||
from rhodecode.lib.vcs.backends.base import EmptyCommit
|
||||
from rhodecode.lib.vcs.exceptions import (
|
||||
RepositoryError, CommitDoesNotExistError)
|
||||
|
|
@ -115,6 +115,7 @@ class RepoCommitsView(RepoAppView):
|
|||
except Exception:
|
||||
log.exception("General failure")
|
||||
raise HTTPNotFound()
|
||||
single_commit = len(c.commit_ranges) == 1
|
||||
|
||||
c.changes = OrderedDict()
|
||||
c.lines_added = 0
|
||||
|
|
@ -128,23 +129,48 @@ class RepoCommitsView(RepoAppView):
|
|||
c.inline_comments = []
|
||||
c.files = []
|
||||
|
||||
c.statuses = []
|
||||
c.comments = []
|
||||
c.unresolved_comments = []
|
||||
c.resolved_comments = []
|
||||
if len(c.commit_ranges) == 1:
|
||||
|
||||
# Single commit
|
||||
if single_commit:
|
||||
commit = c.commit_ranges[0]
|
||||
c.comments = CommentsModel().get_comments(
|
||||
self.db_repo.repo_id,
|
||||
revision=commit.raw_id)
|
||||
c.statuses.append(ChangesetStatusModel().get_status(
|
||||
self.db_repo.repo_id, commit.raw_id))
|
||||
|
||||
# comments from PR
|
||||
statuses = ChangesetStatusModel().get_statuses(
|
||||
self.db_repo.repo_id, commit.raw_id,
|
||||
with_revisions=True)
|
||||
prs = set(st.pull_request for st in statuses
|
||||
if st.pull_request is not None)
|
||||
|
||||
prs = set()
|
||||
reviewers = list()
|
||||
reviewers_duplicates = set() # to not have duplicates from multiple votes
|
||||
for c_status in statuses:
|
||||
|
||||
# extract associated pull-requests from votes
|
||||
if c_status.pull_request:
|
||||
prs.add(c_status.pull_request)
|
||||
|
||||
# extract reviewers
|
||||
_user_id = c_status.author.user_id
|
||||
if _user_id not in reviewers_duplicates:
|
||||
reviewers.append(
|
||||
StrictAttributeDict({
|
||||
'user': c_status.author,
|
||||
|
||||
# fake attributed for commit, page that we don't have
|
||||
# but we share the display with PR page
|
||||
'mandatory': False,
|
||||
'reasons': [],
|
||||
'rule_user_group_data': lambda: None
|
||||
})
|
||||
)
|
||||
reviewers_duplicates.add(_user_id)
|
||||
|
||||
c.allowed_reviewers = reviewers
|
||||
# from associated statuses, check the pull requests, and
|
||||
# show comments from them
|
||||
for pr in prs:
|
||||
|
|
@ -155,6 +181,37 @@ class RepoCommitsView(RepoAppView):
|
|||
c.resolved_comments = CommentsModel()\
|
||||
.get_commit_resolved_todos(commit.raw_id)
|
||||
|
||||
c.inline_comments_flat = CommentsModel()\
|
||||
.get_commit_inline_comments(commit.raw_id)
|
||||
|
||||
review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
|
||||
statuses, reviewers)
|
||||
|
||||
c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
|
||||
|
||||
c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
|
||||
|
||||
for review_obj, member, reasons, mandatory, status in review_statuses:
|
||||
member_reviewer = h.reviewer_as_json(
|
||||
member, reasons=reasons, mandatory=mandatory,
|
||||
user_group=None
|
||||
)
|
||||
|
||||
current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
|
||||
member_reviewer['review_status'] = current_review_status
|
||||
member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
|
||||
member_reviewer['allowed_to_update'] = False
|
||||
c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
|
||||
|
||||
c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
|
||||
|
||||
# NOTE(marcink): this uses the same voting logic as in pull-requests
|
||||
c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
|
||||
c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
|
||||
c.repo_name,
|
||||
commit.raw_id
|
||||
)
|
||||
|
||||
diff = None
|
||||
# Iterate over ranges (default commit view is always one commit)
|
||||
for commit in c.commit_ranges:
|
||||
|
|
@ -166,8 +223,8 @@ class RepoCommitsView(RepoAppView):
|
|||
if method == 'show':
|
||||
inline_comments = CommentsModel().get_inline_comments(
|
||||
self.db_repo.repo_id, revision=commit.raw_id)
|
||||
c.inline_cnt = CommentsModel().get_inline_comments_count(
|
||||
inline_comments)
|
||||
c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
|
||||
inline_comments))
|
||||
c.inline_comments = inline_comments
|
||||
|
||||
cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
|
||||
|
|
@ -226,6 +283,7 @@ class RepoCommitsView(RepoAppView):
|
|||
|
||||
# sort comments by how they were generated
|
||||
c.comments = sorted(c.comments, key=lambda x: x.comment_id)
|
||||
c.at_version_num = None
|
||||
|
||||
if len(c.commit_ranges) == 1:
|
||||
c.commit = c.commit_ranges[0]
|
||||
|
|
@ -395,6 +453,7 @@ class RepoCommitsView(RepoAppView):
|
|||
}
|
||||
if comment:
|
||||
c.co = comment
|
||||
c.at_version_num = 0
|
||||
rendered_comment = render(
|
||||
'rhodecode:templates/changeset/changeset_comment_block.mako',
|
||||
self._get_template_context(c), self.request)
|
||||
|
|
@ -427,7 +486,6 @@ class RepoCommitsView(RepoAppView):
|
|||
return ''
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
'repository.read', 'repository.write', 'repository.admin')
|
||||
@CSRFRequired()
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ from rhodecode.lib.ext_json import json
|
|||
from rhodecode.lib.auth import (
|
||||
LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
|
||||
NotAnonymous, CSRFRequired)
|
||||
from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
|
||||
from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int
|
||||
from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
|
||||
from rhodecode.lib.vcs.exceptions import (
|
||||
CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
|
||||
|
|
@ -265,6 +265,36 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
return diffset
|
||||
|
||||
def register_comments_vars(self, c, pull_request, versions):
|
||||
comments_model = CommentsModel()
|
||||
|
||||
# GENERAL COMMENTS with versions #
|
||||
q = comments_model._all_general_comments_of_pull_request(pull_request)
|
||||
q = q.order_by(ChangesetComment.comment_id.asc())
|
||||
general_comments = q
|
||||
|
||||
# pick comments we want to render at current version
|
||||
c.comment_versions = comments_model.aggregate_comments(
|
||||
general_comments, versions, c.at_version_num)
|
||||
|
||||
# INLINE COMMENTS with versions #
|
||||
q = comments_model._all_inline_comments_of_pull_request(pull_request)
|
||||
q = q.order_by(ChangesetComment.comment_id.asc())
|
||||
inline_comments = q
|
||||
|
||||
c.inline_versions = comments_model.aggregate_comments(
|
||||
inline_comments, versions, c.at_version_num, inline=True)
|
||||
|
||||
# Comments inline+general
|
||||
if c.at_version:
|
||||
c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
|
||||
c.comments = c.comment_versions[c.at_version_num]['display']
|
||||
else:
|
||||
c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
|
||||
c.comments = c.comment_versions[c.at_version_num]['until']
|
||||
|
||||
return general_comments, inline_comments
|
||||
|
||||
@LoginRequired()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
'repository.read', 'repository.write', 'repository.admin')
|
||||
|
|
@ -280,6 +310,8 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
pull_request_id = pull_request.pull_request_id
|
||||
|
||||
c.state_progressing = pull_request.is_state_changing()
|
||||
c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
|
||||
pull_request.target_repo.repo_name, pull_request.pull_request_id)
|
||||
|
||||
_new_state = {
|
||||
'created': PullRequest.STATE_CREATED,
|
||||
|
|
@ -300,22 +332,23 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
from_version = self.request.GET.get('from_version') or version
|
||||
merge_checks = self.request.GET.get('merge_checks')
|
||||
c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
|
||||
force_refresh = str2bool(self.request.GET.get('force_refresh'))
|
||||
c.range_diff_on = self.request.GET.get('range-diff') == "1"
|
||||
|
||||
# fetch global flags of ignore ws or context lines
|
||||
diff_context = diffs.get_diff_context(self.request)
|
||||
hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
|
||||
|
||||
force_refresh = str2bool(self.request.GET.get('force_refresh'))
|
||||
|
||||
(pull_request_latest,
|
||||
pull_request_at_ver,
|
||||
pull_request_display_obj,
|
||||
at_version) = PullRequestModel().get_pr_version(
|
||||
pull_request_id, version=version)
|
||||
|
||||
pr_closed = pull_request_latest.is_closed()
|
||||
|
||||
if pr_closed and (version or from_version):
|
||||
# not allow to browse versions
|
||||
# not allow to browse versions for closed PR
|
||||
raise HTTPFound(h.route_path(
|
||||
'pullrequest_show', repo_name=self.db_repo_name,
|
||||
pull_request_id=pull_request_id))
|
||||
|
|
@ -323,13 +356,13 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
versions = pull_request_display_obj.versions()
|
||||
# used to store per-commit range diffs
|
||||
c.changes = collections.OrderedDict()
|
||||
c.range_diff_on = self.request.GET.get('range-diff') == "1"
|
||||
|
||||
c.at_version = at_version
|
||||
c.at_version_num = (at_version
|
||||
if at_version and at_version != 'latest'
|
||||
if at_version and at_version != PullRequest.LATEST_VER
|
||||
else None)
|
||||
c.at_version_pos = ChangesetComment.get_index_from_version(
|
||||
|
||||
c.at_version_index = ChangesetComment.get_index_from_version(
|
||||
c.at_version_num, versions)
|
||||
|
||||
(prev_pull_request_latest,
|
||||
|
|
@ -340,9 +373,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
c.from_version = prev_at_version
|
||||
c.from_version_num = (prev_at_version
|
||||
if prev_at_version and prev_at_version != 'latest'
|
||||
if prev_at_version and prev_at_version != PullRequest.LATEST_VER
|
||||
else None)
|
||||
c.from_version_pos = ChangesetComment.get_index_from_version(
|
||||
c.from_version_index = ChangesetComment.get_index_from_version(
|
||||
c.from_version_num, versions)
|
||||
|
||||
# define if we're in COMPARE mode or VIEW at version mode
|
||||
|
|
@ -351,16 +384,21 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
# pull_requests repo_name we opened it against
|
||||
# ie. target_repo must match
|
||||
if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
|
||||
log.warning('Mismatch between the current repo: %s, and target %s',
|
||||
self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
|
||||
raise HTTPNotFound()
|
||||
|
||||
c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
|
||||
pull_request_at_ver)
|
||||
c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
|
||||
|
||||
c.pull_request = pull_request_display_obj
|
||||
c.renderer = pull_request_at_ver.description_renderer or c.renderer
|
||||
c.pull_request_latest = pull_request_latest
|
||||
|
||||
if compare or (at_version and not at_version == 'latest'):
|
||||
# inject latest version
|
||||
latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
|
||||
c.versions = versions + [latest_ver]
|
||||
|
||||
if compare or (at_version and not at_version == PullRequest.LATEST_VER):
|
||||
c.allowed_to_change_status = False
|
||||
c.allowed_to_update = False
|
||||
c.allowed_to_merge = False
|
||||
|
|
@ -389,12 +427,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'rules' in pull_request_latest.reviewer_data:
|
||||
rules = pull_request_latest.reviewer_data['rules'] or {}
|
||||
try:
|
||||
c.forbid_adding_reviewers = rules.get(
|
||||
'forbid_adding_reviewers')
|
||||
c.forbid_author_to_review = rules.get(
|
||||
'forbid_author_to_review')
|
||||
c.forbid_commit_author_to_review = rules.get(
|
||||
'forbid_commit_author_to_review')
|
||||
c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
|
||||
c.forbid_author_to_review = rules.get('forbid_author_to_review')
|
||||
c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -419,41 +454,34 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
|
||||
return self._get_template_context(c)
|
||||
|
||||
comments_model = CommentsModel()
|
||||
c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
|
||||
|
||||
# reviewers and statuses
|
||||
c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
|
||||
allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
|
||||
c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
|
||||
c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
|
||||
|
||||
# GENERAL COMMENTS with versions #
|
||||
q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
|
||||
q = q.order_by(ChangesetComment.comment_id.asc())
|
||||
general_comments = q
|
||||
for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
|
||||
member_reviewer = h.reviewer_as_json(
|
||||
member, reasons=reasons, mandatory=mandatory,
|
||||
user_group=review_obj.rule_user_group_data()
|
||||
)
|
||||
|
||||
# pick comments we want to render at current version
|
||||
c.comment_versions = comments_model.aggregate_comments(
|
||||
general_comments, versions, c.at_version_num)
|
||||
c.comments = c.comment_versions[c.at_version_num]['until']
|
||||
current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
|
||||
member_reviewer['review_status'] = current_review_status
|
||||
member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
|
||||
member_reviewer['allowed_to_update'] = c.allowed_to_update
|
||||
c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
|
||||
|
||||
# INLINE COMMENTS with versions #
|
||||
q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
|
||||
q = q.order_by(ChangesetComment.comment_id.asc())
|
||||
inline_comments = q
|
||||
c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
|
||||
|
||||
c.inline_versions = comments_model.aggregate_comments(
|
||||
inline_comments, versions, c.at_version_num, inline=True)
|
||||
general_comments, inline_comments = \
|
||||
self.register_comments_vars(c, pull_request_latest, versions)
|
||||
|
||||
# TODOs
|
||||
c.unresolved_comments = CommentsModel() \
|
||||
.get_pull_request_unresolved_todos(pull_request)
|
||||
.get_pull_request_unresolved_todos(pull_request_latest)
|
||||
c.resolved_comments = CommentsModel() \
|
||||
.get_pull_request_resolved_todos(pull_request)
|
||||
|
||||
# inject latest version
|
||||
latest_ver = PullRequest.get_pr_display_object(
|
||||
pull_request_latest, pull_request_latest)
|
||||
|
||||
c.versions = versions + [latest_ver]
|
||||
.get_pull_request_resolved_todos(pull_request_latest)
|
||||
|
||||
# if we use version, then do not show later comments
|
||||
# than current version
|
||||
|
|
@ -520,8 +548,8 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
# empty version means latest, so we keep this to prevent
|
||||
# double caching
|
||||
version_normalized = version or 'latest'
|
||||
from_version_normalized = from_version or 'latest'
|
||||
version_normalized = version or PullRequest.LATEST_VER
|
||||
from_version_normalized = from_version or PullRequest.LATEST_VER
|
||||
|
||||
cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
|
||||
cache_file_path = diff_cache_exist(
|
||||
|
|
@ -613,7 +641,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
diff_limit, file_limit, c.fulldiff,
|
||||
hide_whitespace_changes, diff_context,
|
||||
use_ancestor=use_ancestor
|
||||
)
|
||||
)
|
||||
|
||||
# save cached diff
|
||||
if caching_enabled:
|
||||
|
|
@ -717,7 +745,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
# current user review statuses for each version
|
||||
c.review_versions = {}
|
||||
if self._rhodecode_user.user_id in allowed_reviewers:
|
||||
if self._rhodecode_user.user_id in c.allowed_reviewers:
|
||||
for co in general_comments:
|
||||
if co.author.user_id == self._rhodecode_user.user_id:
|
||||
status = co.status_change
|
||||
|
|
@ -933,6 +961,90 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
}
|
||||
return data
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
'repository.read', 'repository.write', 'repository.admin')
|
||||
@view_config(
|
||||
route_name='pullrequest_comments', request_method='POST',
|
||||
renderer='string', xhr=True)
|
||||
def pullrequest_comments(self):
|
||||
self.load_default_context()
|
||||
|
||||
pull_request = PullRequest.get_or_404(
|
||||
self.request.matchdict['pull_request_id'])
|
||||
pull_request_id = pull_request.pull_request_id
|
||||
version = self.request.GET.get('version')
|
||||
|
||||
_render = self.request.get_partial_renderer(
|
||||
'rhodecode:templates/base/sidebar.mako')
|
||||
c = _render.get_call_context()
|
||||
|
||||
(pull_request_latest,
|
||||
pull_request_at_ver,
|
||||
pull_request_display_obj,
|
||||
at_version) = PullRequestModel().get_pr_version(
|
||||
pull_request_id, version=version)
|
||||
versions = pull_request_display_obj.versions()
|
||||
latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
|
||||
c.versions = versions + [latest_ver]
|
||||
|
||||
c.at_version = at_version
|
||||
c.at_version_num = (at_version
|
||||
if at_version and at_version != PullRequest.LATEST_VER
|
||||
else None)
|
||||
|
||||
self.register_comments_vars(c, pull_request_latest, versions)
|
||||
all_comments = c.inline_comments_flat + c.comments
|
||||
|
||||
existing_ids = filter(
|
||||
lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
|
||||
return _render('comments_table', all_comments, len(all_comments),
|
||||
existing_ids=existing_ids)
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
'repository.read', 'repository.write', 'repository.admin')
|
||||
@view_config(
|
||||
route_name='pullrequest_todos', request_method='POST',
|
||||
renderer='string', xhr=True)
|
||||
def pullrequest_todos(self):
|
||||
self.load_default_context()
|
||||
|
||||
pull_request = PullRequest.get_or_404(
|
||||
self.request.matchdict['pull_request_id'])
|
||||
pull_request_id = pull_request.pull_request_id
|
||||
version = self.request.GET.get('version')
|
||||
|
||||
_render = self.request.get_partial_renderer(
|
||||
'rhodecode:templates/base/sidebar.mako')
|
||||
c = _render.get_call_context()
|
||||
(pull_request_latest,
|
||||
pull_request_at_ver,
|
||||
pull_request_display_obj,
|
||||
at_version) = PullRequestModel().get_pr_version(
|
||||
pull_request_id, version=version)
|
||||
versions = pull_request_display_obj.versions()
|
||||
latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
|
||||
c.versions = versions + [latest_ver]
|
||||
|
||||
c.at_version = at_version
|
||||
c.at_version_num = (at_version
|
||||
if at_version and at_version != PullRequest.LATEST_VER
|
||||
else None)
|
||||
|
||||
c.unresolved_comments = CommentsModel() \
|
||||
.get_pull_request_unresolved_todos(pull_request)
|
||||
c.resolved_comments = CommentsModel() \
|
||||
.get_pull_request_resolved_todos(pull_request)
|
||||
|
||||
all_comments = c.unresolved_comments + c.resolved_comments
|
||||
existing_ids = filter(
|
||||
lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
|
||||
return _render('comments_table', all_comments, len(c.unresolved_comments),
|
||||
todo_comments=True, existing_ids=existing_ids)
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
|
|
@ -1098,7 +1210,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
self.request.matchdict['pull_request_id'])
|
||||
_ = self.request.translate
|
||||
|
||||
self.load_default_context()
|
||||
c = self.load_default_context()
|
||||
redirect_url = None
|
||||
|
||||
if pull_request.is_closed():
|
||||
|
|
@ -1109,6 +1221,8 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'redirect_url': redirect_url}
|
||||
|
||||
is_state_changing = pull_request.is_state_changing()
|
||||
c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
|
||||
pull_request.target_repo.repo_name, pull_request.pull_request_id)
|
||||
|
||||
# only owner or admin can update it
|
||||
allowed_to_update = PullRequestModel().check_user_update(
|
||||
|
|
@ -1132,7 +1246,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
return {'response': True,
|
||||
'redirect_url': redirect_url}
|
||||
|
||||
self._update_commits(pull_request)
|
||||
self._update_commits(c, pull_request)
|
||||
if force_refresh:
|
||||
redirect_url = h.route_path(
|
||||
'pullrequest_show', repo_name=self.db_repo_name,
|
||||
|
|
@ -1168,7 +1282,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
h.flash(msg, category='success')
|
||||
return
|
||||
|
||||
def _update_commits(self, pull_request):
|
||||
def _update_commits(self, c, pull_request):
|
||||
_ = self.request.translate
|
||||
|
||||
with pull_request.set_state(PullRequest.STATE_UPDATING):
|
||||
|
|
@ -1196,13 +1310,18 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
change_source=changed)
|
||||
h.flash(msg, category='success')
|
||||
|
||||
channel = '/repo${}$/pr/{}'.format(
|
||||
pull_request.target_repo.repo_name, pull_request.pull_request_id)
|
||||
message = msg + (
|
||||
' - <a onclick="window.location.reload()">'
|
||||
'<strong>{}</strong></a>'.format(_('Reload page')))
|
||||
|
||||
message_obj = {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
channelstream.post_message(
|
||||
channel, message, self._rhodecode_user.username,
|
||||
c.pr_broadcast_channel, message_obj, self._rhodecode_user.username,
|
||||
registry=self.request.registry)
|
||||
else:
|
||||
msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
|
||||
|
|
@ -1472,6 +1591,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
}
|
||||
if comment:
|
||||
c.co = comment
|
||||
c.at_version_num = None
|
||||
rendered_comment = render(
|
||||
'rhodecode:templates/changeset/changeset_comment_block.mako',
|
||||
self._get_template_context(c), self.request)
|
||||
|
|
|
|||
|
|
@ -1890,7 +1890,7 @@
|
|||
"url": "http://spdx.org/licenses/BSD-4-Clause.html"
|
||||
}
|
||||
],
|
||||
"name": "python2.7-channelstream-0.5.2"
|
||||
"name": "python2.7-channelstream-0.6.14"
|
||||
},
|
||||
{
|
||||
"license": [
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
|
|||
from rhodecode.lib.exc_tracking import store_exception
|
||||
from rhodecode.subscribers import (
|
||||
scan_repositories_if_enabled, write_js_routes_if_enabled,
|
||||
write_metadata_if_needed, inject_app_settings)
|
||||
write_metadata_if_needed, write_usage_data, inject_app_settings)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -316,6 +316,8 @@ def includeme(config):
|
|||
pyramid.events.ApplicationCreated)
|
||||
config.add_subscriber(write_metadata_if_needed,
|
||||
pyramid.events.ApplicationCreated)
|
||||
config.add_subscriber(write_usage_data,
|
||||
pyramid.events.ApplicationCreated)
|
||||
config.add_subscriber(write_js_routes_if_enabled,
|
||||
pyramid.events.ApplicationCreated)
|
||||
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class PullRequestCommentEvent(PullRequestEvent):
|
|||
|
||||
status = None
|
||||
if self.comment.status_change:
|
||||
status = self.comment.status_change[0].status
|
||||
status = self.comment.review_status
|
||||
|
||||
data.update({
|
||||
'comment': {
|
||||
|
|
@ -184,7 +184,7 @@ class PullRequestCommentEditEvent(PullRequestEvent):
|
|||
|
||||
status = None
|
||||
if self.comment.status_change:
|
||||
status = self.comment.status_change[0].status
|
||||
status = self.comment.review_status
|
||||
|
||||
data.update({
|
||||
'comment': {
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ log = logging.getLogger(__name__)
|
|||
|
||||
LOCK = ReadWriteMutex()
|
||||
|
||||
STATE_PUBLIC_KEYS = ['id', 'username', 'first_name', 'last_name',
|
||||
'icon_link', 'display_name', 'display_link']
|
||||
USER_STATE_PUBLIC_KEYS = [
|
||||
'id', 'username', 'first_name', 'last_name',
|
||||
'icon_link', 'display_name', 'display_link']
|
||||
|
||||
|
||||
class ChannelstreamException(Exception):
|
||||
|
|
@ -64,6 +65,8 @@ def channelstream_request(config, payload, endpoint, raise_exc=True):
|
|||
'x-channelstream-endpoint': endpoint,
|
||||
'Content-Type': 'application/json'}
|
||||
req_url = get_channelstream_server_url(config, endpoint)
|
||||
|
||||
log.debug('Sending a channelstream request to endpoint: `%s`', req_url)
|
||||
response = None
|
||||
try:
|
||||
response = requests.post(req_url, data=json.dumps(payload),
|
||||
|
|
@ -76,6 +79,7 @@ def channelstream_request(config, payload, endpoint, raise_exc=True):
|
|||
log.exception('Exception related to Channelstream happened')
|
||||
if raise_exc:
|
||||
raise ChannelstreamConnectionException()
|
||||
log.debug('Got channelstream response: %s', response)
|
||||
return response
|
||||
|
||||
|
||||
|
|
@ -154,7 +158,7 @@ def parse_channels_info(info_result, include_channel_info=None):
|
|||
for userinfo in info_result['users']:
|
||||
user_state_dict[userinfo['user']] = {
|
||||
k: v for k, v in userinfo['state'].items()
|
||||
if k in STATE_PUBLIC_KEYS
|
||||
if k in USER_STATE_PUBLIC_KEYS
|
||||
}
|
||||
|
||||
channels_info = {}
|
||||
|
|
@ -163,10 +167,10 @@ def parse_channels_info(info_result, include_channel_info=None):
|
|||
if c_name not in include_channel_info:
|
||||
continue
|
||||
connected_list = []
|
||||
for userinfo in c_info['users']:
|
||||
for username in c_info['users']:
|
||||
connected_list.append({
|
||||
'user': userinfo['user'],
|
||||
'state': user_state_dict[userinfo['user']]
|
||||
'user': username,
|
||||
'state': user_state_dict[username]
|
||||
})
|
||||
channels_info[c_name] = {'users': connected_list,
|
||||
'history': c_info['history']}
|
||||
|
|
@ -230,6 +234,14 @@ def get_connection_validators(registry):
|
|||
|
||||
def post_message(channel, message, username, registry=None):
|
||||
|
||||
message_obj = message
|
||||
if isinstance(message, basestring):
|
||||
message_obj = {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
if not registry:
|
||||
registry = get_current_registry()
|
||||
|
||||
|
|
@ -243,11 +255,7 @@ def post_message(channel, message, username, registry=None):
|
|||
'user': 'system',
|
||||
'exclude_users': [username],
|
||||
'channel': channel,
|
||||
'message': {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
'message': message_obj
|
||||
}
|
||||
|
||||
try:
|
||||
|
|
|
|||
5689
rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py
Normal file
5689
rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py
Normal file
File diff suppressed because it is too large
Load diff
52
rhodecode/lib/dbmigrate/versions/109_version_4_21_1.py
Normal file
52
rhodecode/lib/dbmigrate/versions/109_version_4_21_1.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from sqlalchemy import *
|
||||
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
|
||||
from rhodecode.lib.dbmigrate.versions import _reset_base
|
||||
from rhodecode.model import meta, init_model_encryption
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
"""
|
||||
Upgrade operations go here.
|
||||
Don't create your own engine; bind migrate_engine to your metadata
|
||||
"""
|
||||
_reset_base(migrate_engine)
|
||||
from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db
|
||||
|
||||
init_model_encryption(db)
|
||||
|
||||
context = MigrationContext.configure(migrate_engine.connect())
|
||||
op = Operations(context)
|
||||
|
||||
table = db.PullRequestReviewers.__table__
|
||||
with op.batch_alter_table(table.name) as batch_op:
|
||||
new_column = Column('role', Unicode(255), nullable=True)
|
||||
batch_op.add_column(new_column)
|
||||
|
||||
_fill_reviewers_role(db, op, meta.Session)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
|
||||
def fixups(models, _SESSION):
|
||||
pass
|
||||
|
||||
|
||||
def _fill_reviewers_role(models, op, session):
|
||||
params = {'role': 'reviewer'}
|
||||
query = text(
|
||||
'UPDATE pull_request_reviewers SET role = :role'
|
||||
).bindparams(**params)
|
||||
op.execute(query)
|
||||
session().commit()
|
||||
|
|
@ -90,7 +90,7 @@ from rhodecode.lib.vcs.conf.settings import ARCHIVE_SPECS
|
|||
from rhodecode.lib.index.search_utils import get_matching_line_offsets
|
||||
from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
|
||||
from rhodecode.model.changeset_status import ChangesetStatusModel
|
||||
from rhodecode.model.db import Permission, User, Repository, UserApiKeys
|
||||
from rhodecode.model.db import Permission, User, Repository, UserApiKeys, FileStore
|
||||
from rhodecode.model.repo_group import RepoGroupModel
|
||||
from rhodecode.model.settings import IssueTrackerSettingsModel
|
||||
|
||||
|
|
@ -810,8 +810,7 @@ import tzlocal
|
|||
local_timezone = tzlocal.get_localzone()
|
||||
|
||||
|
||||
def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
|
||||
title = value or format_date(datetime_iso)
|
||||
def get_timezone(datetime_iso, time_is_local=False):
|
||||
tzinfo = '+00:00'
|
||||
|
||||
# detect if we have a timezone info, otherwise, add it
|
||||
|
|
@ -822,6 +821,12 @@ def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
|
|||
timezone = force_timezone or local_timezone
|
||||
offset = timezone.localize(datetime_iso).strftime('%z')
|
||||
tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
|
||||
return tzinfo
|
||||
|
||||
|
||||
def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
|
||||
title = value or format_date(datetime_iso)
|
||||
tzinfo = get_timezone(datetime_iso, time_is_local=time_is_local)
|
||||
|
||||
return literal(
|
||||
'<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
|
||||
|
|
@ -1357,20 +1362,76 @@ class InitialsGravatar(object):
|
|||
return "data:image/svg+xml;base64,%s" % base64.b64encode(img_data)
|
||||
|
||||
|
||||
def initials_gravatar(email_address, first_name, last_name, size=30):
|
||||
def initials_gravatar(request, email_address, first_name, last_name, size=30, store_on_disk=False):
|
||||
|
||||
svg_type = None
|
||||
if email_address == User.DEFAULT_USER_EMAIL:
|
||||
svg_type = 'default_user'
|
||||
|
||||
klass = InitialsGravatar(email_address, first_name, last_name, size)
|
||||
return klass.generate_svg(svg_type=svg_type)
|
||||
|
||||
if store_on_disk:
|
||||
from rhodecode.apps.file_store import utils as store_utils
|
||||
from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
|
||||
FileOverSizeException
|
||||
from rhodecode.model.db import Session
|
||||
|
||||
image_key = md5_safe(email_address.lower()
|
||||
+ first_name.lower() + last_name.lower())
|
||||
|
||||
storage = store_utils.get_file_storage(request.registry.settings)
|
||||
filename = '{}.svg'.format(image_key)
|
||||
subdir = 'gravatars'
|
||||
# since final name has a counter, we apply the 0
|
||||
uid = storage.apply_counter(0, store_utils.uid_filename(filename, randomized=False))
|
||||
store_uid = os.path.join(subdir, uid)
|
||||
|
||||
db_entry = FileStore.get_by_store_uid(store_uid)
|
||||
if db_entry:
|
||||
return request.route_path('download_file', fid=store_uid)
|
||||
|
||||
img_data = klass.get_img_data(svg_type=svg_type)
|
||||
img_file = store_utils.bytes_to_file_obj(img_data)
|
||||
|
||||
try:
|
||||
store_uid, metadata = storage.save_file(
|
||||
img_file, filename, directory=subdir,
|
||||
extensions=['.svg'], randomized_name=False)
|
||||
except (FileNotAllowedException, FileOverSizeException):
|
||||
raise
|
||||
|
||||
try:
|
||||
entry = FileStore.create(
|
||||
file_uid=store_uid, filename=metadata["filename"],
|
||||
file_hash=metadata["sha256"], file_size=metadata["size"],
|
||||
file_display_name=filename,
|
||||
file_description=u'user gravatar `{}`'.format(safe_unicode(filename)),
|
||||
hidden=True, check_acl=False, user_id=1
|
||||
)
|
||||
Session().add(entry)
|
||||
Session().commit()
|
||||
log.debug('Stored upload in DB as %s', entry)
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
return request.route_path('download_file', fid=store_uid)
|
||||
|
||||
else:
|
||||
return klass.generate_svg(svg_type=svg_type)
|
||||
|
||||
|
||||
def gravatar_external(request, gravatar_url_tmpl, email_address, size=30):
|
||||
return safe_str(gravatar_url_tmpl)\
|
||||
.replace('{email}', email_address) \
|
||||
.replace('{md5email}', md5_safe(email_address.lower())) \
|
||||
.replace('{netloc}', request.host) \
|
||||
.replace('{scheme}', request.scheme) \
|
||||
.replace('{size}', safe_str(size))
|
||||
|
||||
|
||||
def gravatar_url(email_address, size=30, request=None):
|
||||
request = get_current_request()
|
||||
request = request or get_current_request()
|
||||
_use_gravatar = request.call_context.visual.use_gravatar
|
||||
_gravatar_url = request.call_context.visual.gravatar_url
|
||||
|
||||
_gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
|
||||
|
||||
email_address = email_address or User.DEFAULT_USER_EMAIL
|
||||
if isinstance(email_address, unicode):
|
||||
|
|
@ -1379,21 +1440,15 @@ def gravatar_url(email_address, size=30, request=None):
|
|||
|
||||
# empty email or default user
|
||||
if not email_address or email_address == User.DEFAULT_USER_EMAIL:
|
||||
return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
|
||||
return initials_gravatar(request, User.DEFAULT_USER_EMAIL, '', '', size=size)
|
||||
|
||||
if _use_gravatar:
|
||||
# TODO: Disuse pyramid thread locals. Think about another solution to
|
||||
# get the host and schema here.
|
||||
request = get_current_request()
|
||||
tmpl = safe_str(_gravatar_url)
|
||||
tmpl = tmpl.replace('{email}', email_address)\
|
||||
.replace('{md5email}', md5_safe(email_address.lower())) \
|
||||
.replace('{netloc}', request.host)\
|
||||
.replace('{scheme}', request.scheme)\
|
||||
.replace('{size}', safe_str(size))
|
||||
return tmpl
|
||||
gravatar_url_tmpl = request.call_context.visual.gravatar_url \
|
||||
or User.DEFAULT_GRAVATAR_URL
|
||||
return gravatar_external(request, gravatar_url_tmpl, email_address, size=size)
|
||||
|
||||
else:
|
||||
return initials_gravatar(email_address, '', '', size=size)
|
||||
return initials_gravatar(request, email_address, '', '', size=size)
|
||||
|
||||
|
||||
def breadcrumb_repo_link(repo):
|
||||
|
|
@ -1560,7 +1615,7 @@ def _process_url_func(match_obj, repo_name, uid, entry,
|
|||
# named regex variables
|
||||
named_vars.update(match_obj.groupdict())
|
||||
_url = string.Template(entry['url']).safe_substitute(**named_vars)
|
||||
desc = string.Template(entry['desc']).safe_substitute(**named_vars)
|
||||
desc = string.Template(escape(entry['desc'])).safe_substitute(**named_vars)
|
||||
hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
|
||||
|
||||
def quote_cleaner(input_str):
|
||||
|
|
@ -1600,17 +1655,18 @@ def get_active_pattern_entries(repo_name):
|
|||
|
||||
pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
|
||||
|
||||
allowed_link_formats = [
|
||||
'html', 'rst', 'markdown', 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
|
||||
|
||||
|
||||
def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
|
||||
|
||||
allowed_formats = ['html', 'rst', 'markdown',
|
||||
'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
|
||||
if link_format not in allowed_formats:
|
||||
if link_format not in allowed_link_formats:
|
||||
raise ValueError('Link format can be only one of:{} got {}'.format(
|
||||
allowed_formats, link_format))
|
||||
allowed_link_formats, link_format))
|
||||
|
||||
if active_entries is None:
|
||||
log.debug('Fetch active patterns for repo: %s', repo_name)
|
||||
log.debug('Fetch active issue tracker patterns for repo: %s', repo_name)
|
||||
active_entries = get_active_pattern_entries(repo_name)
|
||||
|
||||
issues_data = []
|
||||
|
|
@ -1668,7 +1724,8 @@ def process_patterns(text_string, repo_name, link_format='html', active_entries=
|
|||
return new_text, issues_data
|
||||
|
||||
|
||||
def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
|
||||
def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None,
|
||||
issues_container=None):
|
||||
"""
|
||||
Parses given text message and makes proper links.
|
||||
issues are linked to given issue-server, and rest is a commit link
|
||||
|
|
@ -1691,6 +1748,9 @@ def urlify_commit_message(commit_text, repository=None, active_pattern_entries=N
|
|||
new_text, issues = process_patterns(new_text, repository or '',
|
||||
active_entries=active_pattern_entries)
|
||||
|
||||
if issues_container is not None:
|
||||
issues_container.extend(issues)
|
||||
|
||||
return literal(new_text)
|
||||
|
||||
|
||||
|
|
@ -1731,7 +1791,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):
|
||||
repo_name=None, active_pattern_entries=None, issues_container=None):
|
||||
|
||||
def maybe_convert_relative_links(html_source):
|
||||
if relative_urls:
|
||||
|
|
@ -1748,6 +1808,8 @@ def render(source, renderer='rst', mentions=False, relative_urls=None,
|
|||
source, issues = process_patterns(
|
||||
source, repo_name, link_format='rst',
|
||||
active_entries=active_pattern_entries)
|
||||
if issues_container is not None:
|
||||
issues_container.extend(issues)
|
||||
|
||||
return literal(
|
||||
'<div class="rst-block">%s</div>' %
|
||||
|
|
@ -1760,6 +1822,8 @@ def render(source, renderer='rst', mentions=False, relative_urls=None,
|
|||
source, issues = process_patterns(
|
||||
source, repo_name, link_format='markdown',
|
||||
active_entries=active_pattern_entries)
|
||||
if issues_container is not None:
|
||||
issues_container.extend(issues)
|
||||
|
||||
return literal(
|
||||
'<div class="markdown-block">%s</div>' %
|
||||
|
|
|
|||
|
|
@ -139,6 +139,18 @@ def is_vcs_call(environ):
|
|||
return False
|
||||
|
||||
|
||||
def get_path_elem(route_path):
|
||||
if not route_path:
|
||||
return None
|
||||
|
||||
cleaned_route_path = route_path.lstrip('/')
|
||||
if cleaned_route_path:
|
||||
cleaned_route_path_elems = cleaned_route_path.split('/')
|
||||
if cleaned_route_path_elems:
|
||||
return cleaned_route_path_elems[0]
|
||||
return None
|
||||
|
||||
|
||||
def detect_vcs_request(environ, backends):
|
||||
checks = {
|
||||
'hg': (is_hg, SimpleHg),
|
||||
|
|
@ -146,6 +158,17 @@ def detect_vcs_request(environ, backends):
|
|||
'svn': (is_svn, SimpleSvn),
|
||||
}
|
||||
handler = None
|
||||
# List of path views first chunk we don't do any checks
|
||||
white_list = [
|
||||
# e.g /_file_store/download
|
||||
'_file_store'
|
||||
]
|
||||
|
||||
path_info = environ['PATH_INFO']
|
||||
|
||||
if get_path_elem(path_info) in white_list:
|
||||
log.debug('path `%s` in whitelist, skipping...', path_info)
|
||||
return handler
|
||||
|
||||
if VCS_TYPE_KEY in environ:
|
||||
raw_type = environ[VCS_TYPE_KEY]
|
||||
|
|
|
|||
|
|
@ -224,7 +224,10 @@ class RedisAuthSessions(BaseAuthSessions):
|
|||
data = client.get(key)
|
||||
if data:
|
||||
json_data = pickle.loads(data)
|
||||
accessed_time = json_data['_accessed_time']
|
||||
try:
|
||||
accessed_time = json_data['_accessed_time']
|
||||
except KeyError:
|
||||
accessed_time = 0
|
||||
if accessed_time < expiry_time:
|
||||
client.delete(key)
|
||||
deleted_keys += 1
|
||||
|
|
|
|||
|
|
@ -212,10 +212,10 @@ class ChangesetStatusModel(BaseModel):
|
|||
# TODO(marcink): with group voting, how does rejected work,
|
||||
# do we ever get rejected state ?
|
||||
|
||||
if approved_votes_count == reviewers_number:
|
||||
if approved_votes_count and (approved_votes_count == reviewers_number):
|
||||
return ChangesetStatus.STATUS_APPROVED
|
||||
|
||||
if rejected_votes_count == reviewers_number:
|
||||
if rejected_votes_count and (rejected_votes_count == reviewers_number):
|
||||
return ChangesetStatus.STATUS_REJECTED
|
||||
|
||||
return ChangesetStatus.STATUS_UNDER_REVIEW
|
||||
|
|
@ -354,34 +354,37 @@ class ChangesetStatusModel(BaseModel):
|
|||
Session().add(new_status)
|
||||
return new_statuses
|
||||
|
||||
def aggregate_votes_by_user(self, commit_statuses, reviewers_data):
|
||||
|
||||
commit_statuses_map = collections.defaultdict(list)
|
||||
for st in commit_statuses:
|
||||
commit_statuses_map[st.author.username] += [st]
|
||||
|
||||
reviewers = []
|
||||
|
||||
def version(commit_status):
|
||||
return commit_status.version
|
||||
|
||||
for obj in reviewers_data:
|
||||
if not obj.user:
|
||||
continue
|
||||
statuses = commit_statuses_map.get(obj.user.username, None)
|
||||
if statuses:
|
||||
status_groups = itertools.groupby(
|
||||
sorted(statuses, key=version), version)
|
||||
statuses = [(x, list(y)[0]) for x, y in status_groups]
|
||||
|
||||
reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses))
|
||||
|
||||
return reviewers
|
||||
|
||||
def reviewers_statuses(self, pull_request):
|
||||
_commit_statuses = self.get_statuses(
|
||||
pull_request.source_repo,
|
||||
pull_request=pull_request,
|
||||
with_revisions=True)
|
||||
|
||||
commit_statuses = collections.defaultdict(list)
|
||||
for st in _commit_statuses:
|
||||
commit_statuses[st.author.username] += [st]
|
||||
|
||||
pull_request_reviewers = []
|
||||
|
||||
def version(commit_status):
|
||||
return commit_status.version
|
||||
|
||||
for obj in pull_request.reviewers:
|
||||
if not obj.user:
|
||||
continue
|
||||
statuses = commit_statuses.get(obj.user.username, None)
|
||||
if statuses:
|
||||
status_groups = itertools.groupby(
|
||||
sorted(statuses, key=version), version)
|
||||
statuses = [(x, list(y)[0]) for x, y in status_groups]
|
||||
|
||||
pull_request_reviewers.append(
|
||||
(obj, obj.user, obj.reasons, obj.mandatory, statuses))
|
||||
|
||||
return pull_request_reviewers
|
||||
return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers)
|
||||
|
||||
def calculated_review_status(self, pull_request, reviewers_statuses=None):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -91,8 +91,7 @@ class CommentsModel(BaseModel):
|
|||
# group by versions, and count until, and display objects
|
||||
|
||||
comment_groups = collections.defaultdict(list)
|
||||
[comment_groups[
|
||||
_co.pull_request_version_id].append(_co) for _co in comments]
|
||||
[comment_groups[_co.pull_request_version_id].append(_co) for _co in comments]
|
||||
|
||||
def yield_comments(pos):
|
||||
for co in comment_groups[pos]:
|
||||
|
|
@ -229,6 +228,14 @@ class CommentsModel(BaseModel):
|
|||
|
||||
return todos
|
||||
|
||||
def get_commit_inline_comments(self, commit_id):
|
||||
inline_comments = Session().query(ChangesetComment) \
|
||||
.filter(ChangesetComment.line_no != None) \
|
||||
.filter(ChangesetComment.f_path != None) \
|
||||
.filter(ChangesetComment.revision == commit_id)
|
||||
inline_comments = inline_comments.all()
|
||||
return inline_comments
|
||||
|
||||
def _log_audit_action(self, action, action_data, auth_user, comment):
|
||||
audit_logger.store(
|
||||
action=action,
|
||||
|
|
@ -456,38 +463,54 @@ class CommentsModel(BaseModel):
|
|||
else:
|
||||
action = 'repo.commit.comment.create'
|
||||
|
||||
comment_id = comment.comment_id
|
||||
comment_data = comment.get_api_data()
|
||||
|
||||
self._log_audit_action(
|
||||
action, {'data': comment_data}, auth_user, comment)
|
||||
|
||||
msg_url = ''
|
||||
channel = None
|
||||
if commit_obj:
|
||||
msg_url = commit_comment_url
|
||||
repo_name = repo.repo_name
|
||||
channel = u'/repo${}$/commit/{}'.format(
|
||||
repo_name,
|
||||
commit_obj.raw_id
|
||||
)
|
||||
elif pull_request_obj:
|
||||
msg_url = pr_comment_url
|
||||
repo_name = pr_target_repo.repo_name
|
||||
channel = u'/repo${}$/pr/{}'.format(
|
||||
repo_name,
|
||||
pull_request_id
|
||||
pull_request_obj.pull_request_id
|
||||
)
|
||||
|
||||
message = '<strong>{}</strong> {} - ' \
|
||||
'<a onclick="window.location=\'{}\';' \
|
||||
'window.location.reload()">' \
|
||||
'<strong>{}</strong></a>'
|
||||
message = message.format(
|
||||
user.username, _('made a comment'), msg_url,
|
||||
_('Show it now'))
|
||||
if channel:
|
||||
username = user.username
|
||||
message = '<strong>{}</strong> {} #{}, {}'
|
||||
message = message.format(
|
||||
username,
|
||||
_('posted a new comment'),
|
||||
comment_id,
|
||||
_('Refresh the page to see new comments.'))
|
||||
|
||||
channelstream.post_message(
|
||||
channel, message, user.username,
|
||||
registry=get_current_registry())
|
||||
message_obj = {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
channelstream.post_message(
|
||||
channel, message_obj, user.username,
|
||||
registry=get_current_registry())
|
||||
|
||||
message_obj = {
|
||||
'message': None,
|
||||
'user': username,
|
||||
'comment_id': comment_id,
|
||||
'topic': '/comment'
|
||||
}
|
||||
channelstream.post_message(
|
||||
channel, message_obj, user.username,
|
||||
registry=get_current_registry())
|
||||
|
||||
return comment
|
||||
|
||||
|
|
@ -641,16 +664,16 @@ class CommentsModel(BaseModel):
|
|||
q = self._get_inline_comments_query(repo_id, revision, pull_request)
|
||||
return self._group_comments_by_path_and_line_number(q)
|
||||
|
||||
def get_inline_comments_count(self, inline_comments, skip_outdated=True,
|
||||
version=None):
|
||||
inline_cnt = 0
|
||||
def get_inline_comments_as_list(self, inline_comments, skip_outdated=True,
|
||||
version=None):
|
||||
inline_comms = []
|
||||
for fname, per_line_comments in inline_comments.iteritems():
|
||||
for lno, comments in per_line_comments.iteritems():
|
||||
for comm in comments:
|
||||
if not comm.outdated_at_version(version) and skip_outdated:
|
||||
inline_cnt += 1
|
||||
inline_comms.append(comm)
|
||||
|
||||
return inline_cnt
|
||||
return inline_comms
|
||||
|
||||
def get_outdated_comments(self, repo_id, pull_request):
|
||||
# TODO: johbo: Remove `repo_id`, it is not needed to find the comments
|
||||
|
|
|
|||
|
|
@ -3809,6 +3809,10 @@ class ChangesetComment(Base, BaseModel):
|
|||
def outdated(self):
|
||||
return self.display_state == self.COMMENT_OUTDATED
|
||||
|
||||
@property
|
||||
def outdated_js(self):
|
||||
return json.dumps(self.display_state == self.COMMENT_OUTDATED)
|
||||
|
||||
@property
|
||||
def immutable(self):
|
||||
return self.immutable_state == self.OP_IMMUTABLE
|
||||
|
|
@ -3817,16 +3821,35 @@ class ChangesetComment(Base, BaseModel):
|
|||
"""
|
||||
Checks if comment is outdated for given pull request version
|
||||
"""
|
||||
return self.outdated and self.pull_request_version_id != version
|
||||
def version_check():
|
||||
return self.pull_request_version_id and self.pull_request_version_id != version
|
||||
|
||||
if self.is_inline:
|
||||
return self.outdated and version_check()
|
||||
else:
|
||||
# general comments don't have .outdated set, also latest don't have a version
|
||||
return version_check()
|
||||
|
||||
def outdated_at_version_js(self, version):
|
||||
"""
|
||||
Checks if comment is outdated for given pull request version
|
||||
"""
|
||||
return json.dumps(self.outdated_at_version(version))
|
||||
|
||||
def older_than_version(self, version):
|
||||
"""
|
||||
Checks if comment is made from previous version than given
|
||||
"""
|
||||
if version is None:
|
||||
return self.pull_request_version_id is not None
|
||||
return self.pull_request_version != version
|
||||
|
||||
return self.pull_request_version_id < version
|
||||
return self.pull_request_version < version
|
||||
|
||||
def older_than_version_js(self, version):
|
||||
"""
|
||||
Checks if comment is made from previous version than given
|
||||
"""
|
||||
return json.dumps(self.older_than_version(version))
|
||||
|
||||
@property
|
||||
def commit_id(self):
|
||||
|
|
@ -3843,7 +3866,9 @@ class ChangesetComment(Base, BaseModel):
|
|||
|
||||
@property
|
||||
def is_inline(self):
|
||||
return self.line_no and self.f_path
|
||||
if self.line_no and self.f_path:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def last_version(self):
|
||||
|
|
@ -3856,6 +3881,16 @@ class ChangesetComment(Base, BaseModel):
|
|||
return self.get_index_from_version(
|
||||
self.pull_request_version_id, versions)
|
||||
|
||||
@property
|
||||
def review_status(self):
|
||||
if self.status_change:
|
||||
return self.status_change[0].status
|
||||
|
||||
@property
|
||||
def review_status_lbl(self):
|
||||
if self.status_change:
|
||||
return self.status_change[0].status_lbl
|
||||
|
||||
def __repr__(self):
|
||||
if self.comment_id:
|
||||
return '<DB:Comment #%s>' % self.comment_id
|
||||
|
|
@ -4133,6 +4168,23 @@ class _PullRequestBase(BaseModel):
|
|||
def reviewer_data_json(self):
|
||||
return json.dumps(self.reviewer_data)
|
||||
|
||||
@property
|
||||
def last_merge_metadata_parsed(self):
|
||||
metadata = {}
|
||||
if not self.last_merge_metadata:
|
||||
return metadata
|
||||
|
||||
if hasattr(self.last_merge_metadata, 'de_coerce'):
|
||||
for k, v in self.last_merge_metadata.de_coerce().items():
|
||||
if k in ['target_ref', 'source_ref']:
|
||||
metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
|
||||
else:
|
||||
if hasattr(v, 'de_coerce'):
|
||||
metadata[k] = v.de_coerce()
|
||||
else:
|
||||
metadata[k] = v
|
||||
return metadata
|
||||
|
||||
@property
|
||||
def work_in_progress(self):
|
||||
"""checks if pull request is work in progress by checking the title"""
|
||||
|
|
@ -4306,6 +4358,7 @@ class PullRequest(Base, _PullRequestBase):
|
|||
__table_args__ = (
|
||||
base_table_args,
|
||||
)
|
||||
LATEST_VER = 'latest'
|
||||
|
||||
pull_request_id = Column(
|
||||
'pull_request_id', Integer(), nullable=False, primary_key=True)
|
||||
|
|
@ -4364,6 +4417,10 @@ class PullRequest(Base, _PullRequestBase):
|
|||
def pull_request_version_id(self):
|
||||
return getattr(pull_request_obj, 'pull_request_version_id', None)
|
||||
|
||||
@property
|
||||
def pull_request_last_version(self):
|
||||
return pull_request_obj.pull_request_last_version
|
||||
|
||||
attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
|
||||
|
||||
attrs.author = StrictAttributeDict(
|
||||
|
|
@ -4428,6 +4485,10 @@ class PullRequest(Base, _PullRequestBase):
|
|||
"""
|
||||
return self.versions.count() + 1
|
||||
|
||||
@property
|
||||
def pull_request_last_version(self):
|
||||
return self.versions_count
|
||||
|
||||
|
||||
class PullRequestVersion(Base, _PullRequestBase):
|
||||
__tablename__ = 'pull_request_versions'
|
||||
|
|
@ -4475,6 +4536,8 @@ class PullRequestReviewers(Base, BaseModel):
|
|||
__table_args__ = (
|
||||
base_table_args,
|
||||
)
|
||||
ROLE_REVIEWER = u'reviewer'
|
||||
ROLE_OBSERVER = u'observer'
|
||||
|
||||
@hybrid_property
|
||||
def reasons(self):
|
||||
|
|
@ -4502,6 +4565,8 @@ class PullRequestReviewers(Base, BaseModel):
|
|||
JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
|
||||
|
||||
mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
|
||||
role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
|
||||
|
||||
user = relationship('User')
|
||||
pull_request = relationship('PullRequest')
|
||||
|
||||
|
|
@ -5425,8 +5490,11 @@ class FileStore(Base, BaseModel):
|
|||
repo_group = relationship('RepoGroup', lazy='joined')
|
||||
|
||||
@classmethod
|
||||
def get_by_store_uid(cls, file_store_uid):
|
||||
return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
|
||||
def get_by_store_uid(cls, file_store_uid, safe=False):
|
||||
if safe:
|
||||
return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
|
||||
else:
|
||||
return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
|
||||
|
||||
@classmethod
|
||||
def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
|
||||
|
|
|
|||
|
|
@ -1600,7 +1600,7 @@ class PullRequestModel(BaseModel):
|
|||
'source_ref': pull_request.source_ref_parts,
|
||||
}
|
||||
if pull_request.last_merge_metadata:
|
||||
metadata.update(pull_request.last_merge_metadata)
|
||||
metadata.update(pull_request.last_merge_metadata_parsed)
|
||||
|
||||
if not possible and target_ref.type == 'branch':
|
||||
# NOTE(marcink): case for mercurial multiple heads on branch
|
||||
|
|
|
|||
|
|
@ -55,3 +55,16 @@
|
|||
margin: 0 auto 35px auto;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-text-success {
|
||||
color: @alert1;
|
||||
|
||||
}
|
||||
|
||||
.alert-text-error {
|
||||
color: @alert2;
|
||||
}
|
||||
|
||||
.alert-text-warning {
|
||||
color: @alert3;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ input[type="button"] {
|
|||
|
||||
.btn-group-actions {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
z-index: 50;
|
||||
|
||||
&:not(.open) .btn-action-switcher-container {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -1078,10 +1078,16 @@ input.filediff-collapse-state {
|
|||
background: @color5;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&[op="comments"] { /* comments on file */
|
||||
background: @grey4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&[op="options"] { /* context menu */
|
||||
background: @grey6;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ a { cursor: pointer; }
|
|||
clear: both;
|
||||
}
|
||||
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,14 +240,14 @@ div.markdown-block ol {
|
|||
div.markdown-block ul.checkbox li,
|
||||
div.markdown-block ol.checkbox li {
|
||||
list-style: none !important;
|
||||
margin: 6px !important;
|
||||
margin: 0px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
div.markdown-block ul li,
|
||||
div.markdown-block ol li {
|
||||
list-style: disc !important;
|
||||
margin: 6px !important;
|
||||
margin: 0px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,11 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.action-link{
|
||||
margin-left: @padding;
|
||||
padding-left: @padding;
|
||||
|
|
@ -482,10 +487,15 @@ ul.auth_plugins {
|
|||
text-align: left;
|
||||
overflow: hidden;
|
||||
white-space: pre-line;
|
||||
padding-top: 5px
|
||||
}
|
||||
|
||||
.pr-details-title {
|
||||
height: 16px
|
||||
#add_reviewer {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#add_reviewer_input {
|
||||
padding-top: 10px
|
||||
}
|
||||
|
||||
.pr-details-title-author-pref {
|
||||
|
|
@ -1173,9 +1183,12 @@ label {
|
|||
a {
|
||||
color: @grey5
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
|
||||
// 1024px or smaller
|
||||
@media screen and (max-width: 1180px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
img {
|
||||
|
|
@ -1492,26 +1505,17 @@ table.integrations {
|
|||
|
||||
// Pull Requests
|
||||
.summary-details {
|
||||
width: 72%;
|
||||
width: 100%;
|
||||
}
|
||||
.pr-summary {
|
||||
border-bottom: @border-thickness solid @grey5;
|
||||
margin-bottom: @space;
|
||||
}
|
||||
|
||||
.reviewers-title {
|
||||
width: 25%;
|
||||
min-width: 200px;
|
||||
|
||||
&.first-panel {
|
||||
margin-top: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.reviewers {
|
||||
width: 25%;
|
||||
min-width: 200px;
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
.reviewers ul li {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
|
@ -1523,18 +1527,14 @@ table.integrations {
|
|||
min-height: 55px;
|
||||
}
|
||||
|
||||
.reviewers_member {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.reviewer_reason {
|
||||
padding-left: 20px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
.reviewer_status {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
min-width: 25px;
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
height: 1.2em;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
|
@ -1557,25 +1557,20 @@ table.integrations {
|
|||
}
|
||||
|
||||
.reviewer_member_mandatory {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 8px;
|
||||
width: 16px;
|
||||
font-size: 11px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: black;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.reviewer_member_mandatory_remove,
|
||||
.reviewer_member_remove {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 16px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reviewer_member_mandatory_remove {
|
||||
|
|
@ -1593,6 +1588,9 @@ table.integrations {
|
|||
cursor: pointer;
|
||||
}
|
||||
.pr-details-title {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
padding-bottom: 8px;
|
||||
border-bottom: @border-thickness solid @grey5;
|
||||
|
||||
|
|
@ -1617,7 +1615,7 @@ table.integrations {
|
|||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.todo-table {
|
||||
.todo-table, .comments-table {
|
||||
width: 100%;
|
||||
|
||||
td {
|
||||
|
|
@ -1627,7 +1625,8 @@ table.integrations {
|
|||
.td-todo-number {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
width: 15%;
|
||||
width: 1%;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.td-todo-gravatar {
|
||||
|
|
@ -1651,10 +1650,13 @@ table.integrations {
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
table.group_members {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.group_members {
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
list-style: outside none none;
|
||||
|
||||
img {
|
||||
height: @gravatar-size;
|
||||
|
|
@ -1698,7 +1700,7 @@ table.integrations {
|
|||
}
|
||||
|
||||
.reviewer_ac .ac-input {
|
||||
width: 92%;
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
|
|
@ -2772,7 +2774,7 @@ table.rctable td.td-search-results div {
|
|||
}
|
||||
|
||||
#help_kb .modal-content{
|
||||
max-width: 750px;
|
||||
max-width: 800px;
|
||||
margin: 10% auto;
|
||||
|
||||
table{
|
||||
|
|
@ -3069,4 +3071,141 @@ form.markup-form {
|
|||
|
||||
.pr-hovercard-title {
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-divider {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.details-inline-block {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details-inline-block summary {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
details:not([open]) > :not(summary) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.details-reset > summary {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.details-reset > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.details-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 185px;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid @grey5;
|
||||
box-shadow: 0 8px 24px rgba(149, 157, 165, .2);
|
||||
left: -150px;
|
||||
text-align: left;
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
display: block;
|
||||
height: 0;
|
||||
margin: 8px 0;
|
||||
border-top: 1px solid @grey5;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: block;
|
||||
padding: 4px 8px 4px 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background: #fafafa;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
border-left: 1px solid @grey5;
|
||||
}
|
||||
|
||||
.right-sidebar.right-sidebar-expanded {
|
||||
width: 300px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.right-sidebar.right-sidebar-collapsed {
|
||||
width: 40px;
|
||||
padding: 0;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
float: right;
|
||||
will-change: min-height;
|
||||
background: #fafafa;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
margin: 15px 0px 0 0;
|
||||
}
|
||||
|
||||
.sidebar-toggle a {
|
||||
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: 1.2em;
|
||||
font-weight: 700;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.sidebar-element {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.right-sidebar-collapsed-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
font-size: 1.3em;
|
||||
margin: 0 -15px;
|
||||
}
|
||||
|
||||
.right-sidebar-collapsed-state:hover {
|
||||
background-color: @grey5;
|
||||
}
|
||||
|
||||
.old-comments-marker {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.old-comments-marker td {
|
||||
padding-top: 15px;
|
||||
border-bottom: 1px solid @grey5;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -790,7 +790,7 @@ input {
|
|||
|
||||
&.main_filter_input {
|
||||
padding: 5px 10px;
|
||||
min-width: 340px;
|
||||
|
||||
color: @grey7;
|
||||
background: @black;
|
||||
min-height: 18px;
|
||||
|
|
@ -800,11 +800,34 @@ input {
|
|||
color: @grey2 !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
color: @grey2 !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
min-width: 360px;
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
min-width: 300px;
|
||||
}
|
||||
@media screen and (max-width: 1500px) {
|
||||
min-width: 280px;
|
||||
}
|
||||
@media screen and (max-width: 1400px) {
|
||||
min-width: 260px;
|
||||
}
|
||||
@media screen and (max-width: 1300px) {
|
||||
min-width: 240px;
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
min-width: 220px;
|
||||
}
|
||||
@media screen and (max-width: 720px) {
|
||||
min-width: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@
|
|||
.icon-remove:before { content: '\e810'; } /* '' */
|
||||
.icon-fork:before { content: '\e811'; } /* '' */
|
||||
.icon-more:before { content: '\e812'; } /* '' */
|
||||
.icon-options:before { content: '\e812'; } /* '' */
|
||||
.icon-search:before { content: '\e813'; } /* '' */
|
||||
.icon-scissors:before { content: '\e814'; } /* '' */
|
||||
.icon-download:before { content: '\e815'; } /* '' */
|
||||
|
|
@ -251,6 +252,7 @@
|
|||
// TRANSFORM
|
||||
.icon-merge:before {transform: rotate(180deg);}
|
||||
.icon-wide-mode:before {transform: rotate(90deg);}
|
||||
.icon-options:before {transform: rotate(90deg);}
|
||||
|
||||
// -- END ICON CLASSES -- //
|
||||
|
||||
|
|
|
|||
|
|
@ -131,6 +131,11 @@ function setRCMouseBindings(repoName, repoLandingRev) {
|
|||
window.location = pyroutes.url(
|
||||
'edit_repo_perms', {'repo_name': repoName});
|
||||
});
|
||||
Mousetrap.bind(['t s'], function(e) {
|
||||
if (window.toggleSidebar !== undefined) {
|
||||
window.toggleSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,6 +246,8 @@ function registerRCRoutes() {
|
|||
pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
|
||||
pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
|
||||
pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
|
||||
pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
|
||||
pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
|
||||
pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
|
||||
pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
|
||||
pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
|
||||
|
|
|
|||
|
|
@ -28,9 +28,12 @@ export class RhodecodeApp extends PolymerElement {
|
|||
super.connectedCallback();
|
||||
ccLog.debug('rhodeCodeApp created');
|
||||
$.Topic('/notifications').subscribe(this.handleNotifications.bind(this));
|
||||
$.Topic('/comment').subscribe(this.handleComment.bind(this));
|
||||
$.Topic('/favicon/update').subscribe(this.faviconUpdate.bind(this));
|
||||
$.Topic('/connection_controller/subscribe').subscribe(
|
||||
this.subscribeToChannelTopic.bind(this));
|
||||
this.subscribeToChannelTopic.bind(this)
|
||||
);
|
||||
|
||||
// this event can be used to coordinate plugins to do their
|
||||
// initialization before channelstream is kicked off
|
||||
$.Topic('/__MAIN_APP__').publish({});
|
||||
|
|
@ -71,6 +74,14 @@ export class RhodecodeApp extends PolymerElement {
|
|||
|
||||
}
|
||||
|
||||
handleComment(data) {
|
||||
if (data.message.comment_id) {
|
||||
if (window.refreshAllComments !== undefined) {
|
||||
refreshAllComments()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
faviconUpdate(data) {
|
||||
this.shadowRoot.querySelector('rhodecode-favicon').counter = data.count;
|
||||
}
|
||||
|
|
@ -95,6 +106,7 @@ export class RhodecodeApp extends PolymerElement {
|
|||
}
|
||||
// append any additional channels registered in other plugins
|
||||
$.Topic('/connection_controller/subscribe').processPrepared();
|
||||
|
||||
channelstreamConnection.connect();
|
||||
}
|
||||
}
|
||||
|
|
@ -157,8 +169,7 @@ export class RhodecodeApp extends PolymerElement {
|
|||
|
||||
handleConnected(event) {
|
||||
var channelstreamConnection = this.getChannelStreamConnection();
|
||||
channelstreamConnection.set('channelsState',
|
||||
event.detail.channels_info);
|
||||
channelstreamConnection.set('channelsState', event.detail.channels_info);
|
||||
channelstreamConnection.set('userState', event.detail.state);
|
||||
channelstreamConnection.set('channels', event.detail.channels);
|
||||
this.propagageChannelsState();
|
||||
|
|
|
|||
|
|
@ -296,16 +296,25 @@ var tooltipActivate = function () {
|
|||
// we set a variable so the data is only loaded once via Ajax, not every time the tooltip opens
|
||||
if ($origin.data('loaded') !== true) {
|
||||
var hovercardUrl = $origin.data('hovercardUrl');
|
||||
var altHovercard =$origin.data('hovercardAlt');
|
||||
var altHovercard = $origin.data('hovercardAlt');
|
||||
|
||||
if (hovercardUrl !== undefined && hovercardUrl !== "") {
|
||||
if (hovercardUrl.substr(0,12) === 'pyroutes.url'){
|
||||
var urlLoad = true;
|
||||
if (hovercardUrl.substr(0, 12) === 'pyroutes.url') {
|
||||
hovercardUrl = eval(hovercardUrl)
|
||||
} else if (hovercardUrl.substr(0, 11) === 'javascript:') {
|
||||
var jsFunc = hovercardUrl.substr(11);
|
||||
urlLoad = false;
|
||||
loaded = true;
|
||||
instance.content(eval(jsFunc))
|
||||
}
|
||||
|
||||
if (urlLoad) {
|
||||
var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) {
|
||||
instance.content(data);
|
||||
})
|
||||
}
|
||||
|
||||
var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) {
|
||||
instance.content(data);
|
||||
})
|
||||
} else {
|
||||
if ($origin.data('hovercardAltHtml')) {
|
||||
var data = atob($origin.data('hovercardAltHtml'));
|
||||
|
|
@ -677,7 +686,9 @@ var feedLifetimeOptions = function(query, initialData){
|
|||
query.callback(data);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Retrievew via templateContext.session_attrs.key
|
||||
* */
|
||||
var storeUserSessionAttr = function (key, val) {
|
||||
|
||||
var postData = {
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ var CommentsController = function() {
|
|||
return false;
|
||||
};
|
||||
|
||||
this.showVersion = function (comment_id, comment_history_id) {
|
||||
this.showVersion = function (comment_id, comment_history_id) {
|
||||
|
||||
var historyViewUrl = pyroutes.url(
|
||||
'repo_commit_comment_history_view',
|
||||
|
|
@ -585,7 +585,7 @@ var CommentsController = function() {
|
|||
successRenderCommit,
|
||||
failRenderCommit
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
this.getLineNumber = function(node) {
|
||||
var $node = $(node);
|
||||
|
|
@ -670,8 +670,20 @@ var CommentsController = function() {
|
|||
|
||||
var success = function(response) {
|
||||
$comment.remove();
|
||||
|
||||
if (window.updateSticky !== undefined) {
|
||||
// potentially our comments change the active window size, so we
|
||||
// notify sticky elements
|
||||
updateSticky()
|
||||
}
|
||||
|
||||
if (window.refreshAllComments !== undefined) {
|
||||
// if we have this handler, run it, and refresh all comments boxes
|
||||
refreshAllComments()
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var failure = function(jqXHR, textStatus, errorThrown) {
|
||||
var prefix = "Error while deleting this comment.\n"
|
||||
var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
|
||||
|
|
@ -682,6 +694,9 @@ var CommentsController = function() {
|
|||
return false;
|
||||
};
|
||||
ajaxPOST(url, postData, success, failure);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
this.deleteComment = function(node) {
|
||||
|
|
@ -727,6 +742,15 @@ var CommentsController = function() {
|
|||
$filediff.find('.hide-line-comments').removeClass('hide-line-comments');
|
||||
$filediff.toggleClass('hide-comments');
|
||||
}
|
||||
|
||||
// since we change the height of the diff container that has anchor points for upper
|
||||
// sticky header, we need to tell it to re-calculate those
|
||||
if (window.updateSticky !== undefined) {
|
||||
// potentially our comments change the active window size, so we
|
||||
// notify sticky elements
|
||||
updateSticky()
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
@ -747,7 +771,7 @@ var CommentsController = function() {
|
|||
var cm = commentForm.getCmInstance();
|
||||
|
||||
if (resolvesCommentId){
|
||||
var placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
|
||||
placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
|
|
@ -1077,9 +1101,15 @@ var CommentsController = function() {
|
|||
updateSticky()
|
||||
}
|
||||
|
||||
if (window.refreshAllComments !== undefined) {
|
||||
// if we have this handler, run it, and refresh all comments boxes
|
||||
refreshAllComments()
|
||||
}
|
||||
|
||||
commentForm.setActionButtonsDisabled(false);
|
||||
|
||||
};
|
||||
|
||||
var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
|
||||
var prefix = "Error while editing comment.\n"
|
||||
var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
|
||||
|
|
@ -1209,6 +1239,11 @@ var CommentsController = function() {
|
|||
updateSticky()
|
||||
}
|
||||
|
||||
if (window.refreshAllComments !== undefined) {
|
||||
// if we have this handler, run it, and refresh all comments boxes
|
||||
refreshAllComments()
|
||||
}
|
||||
|
||||
commentForm.setActionButtonsDisabled(false);
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,4 +35,75 @@ var quick_repo_menu = function() {
|
|||
}, function() {
|
||||
hide_quick_repo_menus();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
window.toggleElement = function (elem, target) {
|
||||
var $elem = $(elem);
|
||||
var $target = $(target);
|
||||
|
||||
if ($target.is(':visible') || $target.length === 0) {
|
||||
$target.hide();
|
||||
$elem.html($elem.data('toggleOn'))
|
||||
} else {
|
||||
$target.show();
|
||||
$elem.html($elem.data('toggleOff'))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var marginExpVal = '300' // needs a sync with `.right-sidebar.right-sidebar-expanded` value
|
||||
var marginColVal = '40' // needs a sync with `.right-sidebar.right-sidebar-collapsed` value
|
||||
|
||||
var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)};
|
||||
var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)};
|
||||
|
||||
var updateStickyHeader = function () {
|
||||
if (window.updateSticky !== undefined) {
|
||||
// potentially our comments change the active window size, so we
|
||||
// notify sticky elements
|
||||
updateSticky()
|
||||
}
|
||||
}
|
||||
|
||||
var expandSidebar = function () {
|
||||
var $sideBar = $('.right-sidebar');
|
||||
$('.outerwrapper').css(marginExpanded);
|
||||
$('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
|
||||
$('.right-sidebar-collapsed-state').hide();
|
||||
$('.right-sidebar-expanded-state').show();
|
||||
$('.branding').addClass('display-none');
|
||||
$sideBar.addClass('right-sidebar-expanded')
|
||||
$sideBar.removeClass('right-sidebar-collapsed')
|
||||
}
|
||||
|
||||
var collapseSidebar = function () {
|
||||
var $sideBar = $('.right-sidebar');
|
||||
$('.outerwrapper').css(marginCollapsed);
|
||||
$('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
|
||||
$('.right-sidebar-collapsed-state').show();
|
||||
$('.right-sidebar-expanded-state').hide();
|
||||
$('.branding').removeClass('display-none');
|
||||
$sideBar.removeClass('right-sidebar-expanded')
|
||||
$sideBar.addClass('right-sidebar-collapsed')
|
||||
}
|
||||
|
||||
window.toggleSidebar = function () {
|
||||
var $sideBar = $('.right-sidebar');
|
||||
|
||||
if ($sideBar.hasClass('right-sidebar-expanded')) {
|
||||
// expanded -> collapsed transition
|
||||
collapseSidebar();
|
||||
var sidebarState = 'collapsed';
|
||||
|
||||
} else {
|
||||
// collapsed -> expanded
|
||||
expandSidebar();
|
||||
var sidebarState = 'expanded';
|
||||
}
|
||||
|
||||
// update our other sticky header in same context
|
||||
updateStickyHeader();
|
||||
storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,10 +98,13 @@ ReviewersController = function () {
|
|||
var self = this;
|
||||
this.$reviewRulesContainer = $('#review_rules');
|
||||
this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
|
||||
this.$userRule = $('.pr-user-rule-container');
|
||||
this.forbidReviewUsers = undefined;
|
||||
this.$reviewMembers = $('#review_members');
|
||||
this.currentRequest = null;
|
||||
this.diffData = null;
|
||||
this.enabledRules = [];
|
||||
|
||||
//dummy handler, we might register our own later
|
||||
this.diffDataHandler = function(data){};
|
||||
|
||||
|
|
@ -116,14 +119,17 @@ ReviewersController = function () {
|
|||
|
||||
this.hideReviewRules = function () {
|
||||
self.$reviewRulesContainer.hide();
|
||||
$(self.$userRule.selector).hide();
|
||||
};
|
||||
|
||||
this.showReviewRules = function () {
|
||||
self.$reviewRulesContainer.show();
|
||||
$(self.$userRule.selector).show();
|
||||
};
|
||||
|
||||
this.addRule = function (ruleText) {
|
||||
self.showReviewRules();
|
||||
self.enabledRules.push(ruleText);
|
||||
return '<div>- {0}</div>'.format(ruleText)
|
||||
};
|
||||
|
||||
|
|
@ -179,6 +185,7 @@ ReviewersController = function () {
|
|||
_gettext('Reviewers picked from source code changes.'))
|
||||
)
|
||||
}
|
||||
|
||||
if (data.rules.forbid_adding_reviewers) {
|
||||
$('#add_reviewer_input').remove();
|
||||
self.$rulesList.append(
|
||||
|
|
@ -186,6 +193,7 @@ ReviewersController = function () {
|
|||
_gettext('Adding new reviewers is forbidden.'))
|
||||
)
|
||||
}
|
||||
|
||||
if (data.rules.forbid_author_to_review) {
|
||||
self.forbidReviewUsers.push(data.rules_data.pr_author);
|
||||
self.$rulesList.append(
|
||||
|
|
@ -193,6 +201,7 @@ ReviewersController = function () {
|
|||
_gettext('Author is not allowed to be a reviewer.'))
|
||||
)
|
||||
}
|
||||
|
||||
if (data.rules.forbid_commit_author_to_review) {
|
||||
|
||||
if (data.rules_data.forbidden_users) {
|
||||
|
|
@ -208,6 +217,12 @@ ReviewersController = function () {
|
|||
)
|
||||
}
|
||||
|
||||
// we don't have any rules set, so we inform users about it
|
||||
if (self.enabledRules.length === 0) {
|
||||
self.addRule(
|
||||
_gettext('No review rules set.'))
|
||||
}
|
||||
|
||||
return self.forbidReviewUsers
|
||||
};
|
||||
|
||||
|
|
@ -264,8 +279,11 @@ ReviewersController = function () {
|
|||
$('#user').show(); // show user autocomplete after load
|
||||
|
||||
var commitElements = data["diff_info"]['commits'];
|
||||
|
||||
if (commitElements.length === 0) {
|
||||
prButtonLock(true, _gettext('no commits'), 'all');
|
||||
var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
|
||||
_gettext('There are no commits to merge.'));
|
||||
prButtonLock(true, noCommitsMsg, 'all');
|
||||
|
||||
} else {
|
||||
// un-lock PR button, so we cannot send PR before it's calculated
|
||||
|
|
@ -309,7 +327,6 @@ ReviewersController = function () {
|
|||
};
|
||||
|
||||
this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
|
||||
var members = self.$reviewMembers.get(0);
|
||||
var id = reviewer_obj.user_id;
|
||||
var username = reviewer_obj.username;
|
||||
|
||||
|
|
@ -318,10 +335,10 @@ ReviewersController = function () {
|
|||
|
||||
// register IDS to check if we don't have this ID already in
|
||||
var currentIds = [];
|
||||
var _els = self.$reviewMembers.find('li').toArray();
|
||||
for (el in _els) {
|
||||
currentIds.push(_els[el].id)
|
||||
}
|
||||
|
||||
$.each(self.$reviewMembers.find('.reviewer_entry'), function (index, value) {
|
||||
currentIds.push($(value).data('reviewerUserId'))
|
||||
})
|
||||
|
||||
var userAllowedReview = function (userId) {
|
||||
var allowed = true;
|
||||
|
|
@ -339,20 +356,23 @@ ReviewersController = function () {
|
|||
alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
|
||||
} else {
|
||||
// only add if it's not there
|
||||
var alreadyReviewer = currentIds.indexOf('reviewer_' + id) != -1;
|
||||
var alreadyReviewer = currentIds.indexOf(id) != -1;
|
||||
|
||||
if (alreadyReviewer) {
|
||||
alert(_gettext('User `{0}` already in reviewers').format(username));
|
||||
} else {
|
||||
members.innerHTML += renderTemplate('reviewMemberEntry', {
|
||||
var reviewerEntry = renderTemplate('reviewMemberEntry', {
|
||||
'member': reviewer_obj,
|
||||
'mandatory': mandatory,
|
||||
'reasons': reasons,
|
||||
'allowed_to_update': true,
|
||||
'review_status': 'not_reviewed',
|
||||
'review_status_label': _gettext('Not Reviewed'),
|
||||
'reasons': reasons,
|
||||
'create': true
|
||||
});
|
||||
'user_group': reviewer_obj.user_group,
|
||||
'create': true,
|
||||
'rule_show': true,
|
||||
})
|
||||
$(self.$reviewMembers.selector).append(reviewerEntry);
|
||||
tooltipActivate();
|
||||
}
|
||||
}
|
||||
|
|
@ -476,7 +496,7 @@ var ReviewerAutoComplete = function(inputId) {
|
|||
};
|
||||
|
||||
|
||||
VersionController = function () {
|
||||
window.VersionController = function () {
|
||||
var self = this;
|
||||
this.$verSource = $('input[name=ver_source]');
|
||||
this.$verTarget = $('input[name=ver_target]');
|
||||
|
|
@ -596,25 +616,10 @@ VersionController = function () {
|
|||
return false
|
||||
};
|
||||
|
||||
this.toggleElement = function (elem, target) {
|
||||
var $elem = $(elem);
|
||||
var $target = $(target);
|
||||
|
||||
if ($target.is(':visible')) {
|
||||
$target.hide();
|
||||
$elem.html($elem.data('toggleOn'))
|
||||
} else {
|
||||
$target.show();
|
||||
$elem.html($elem.data('toggleOff'))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
UpdatePrController = function () {
|
||||
window.UpdatePrController = function () {
|
||||
var self = this;
|
||||
this.$updateCommits = $('#update_commits');
|
||||
this.$updateCommitsSwitcher = $('#update_commits_switcher');
|
||||
|
|
@ -656,4 +661,230 @@ UpdatePrController = function () {
|
|||
templateContext.repo_name,
|
||||
templateContext.pull_request_data.pull_request_id, force);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Reviewer display panel
|
||||
*/
|
||||
window.ReviewersPanel = {
|
||||
editButton: null,
|
||||
closeButton: null,
|
||||
addButton: null,
|
||||
removeButtons: null,
|
||||
reviewRules: null,
|
||||
setReviewers: null,
|
||||
|
||||
setSelectors: function () {
|
||||
var self = this;
|
||||
self.editButton = $('#open_edit_reviewers');
|
||||
self.closeButton =$('#close_edit_reviewers');
|
||||
self.addButton = $('#add_reviewer');
|
||||
self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
|
||||
},
|
||||
|
||||
init: function (reviewRules, setReviewers) {
|
||||
var self = this;
|
||||
self.setSelectors();
|
||||
|
||||
this.reviewRules = reviewRules;
|
||||
this.setReviewers = setReviewers;
|
||||
|
||||
this.editButton.on('click', function (e) {
|
||||
self.edit();
|
||||
});
|
||||
this.closeButton.on('click', function (e) {
|
||||
self.close();
|
||||
self.renderReviewers();
|
||||
});
|
||||
|
||||
self.renderReviewers();
|
||||
|
||||
},
|
||||
|
||||
renderReviewers: function () {
|
||||
|
||||
$('#review_members').html('')
|
||||
$.each(this.setReviewers.reviewers, function (key, val) {
|
||||
var member = val;
|
||||
|
||||
var entry = renderTemplate('reviewMemberEntry', {
|
||||
'member': member,
|
||||
'mandatory': member.mandatory,
|
||||
'reasons': member.reasons,
|
||||
'allowed_to_update': member.allowed_to_update,
|
||||
'review_status': member.review_status,
|
||||
'review_status_label': member.review_status_label,
|
||||
'user_group': member.user_group,
|
||||
'create': false
|
||||
});
|
||||
|
||||
$('#review_members').append(entry)
|
||||
});
|
||||
tooltipActivate();
|
||||
|
||||
},
|
||||
|
||||
edit: function (event) {
|
||||
this.editButton.hide();
|
||||
this.closeButton.show();
|
||||
this.addButton.show();
|
||||
$(this.removeButtons.selector).css('visibility', 'visible');
|
||||
// review rules
|
||||
reviewersController.loadReviewRules(this.reviewRules);
|
||||
},
|
||||
|
||||
close: function (event) {
|
||||
this.editButton.show();
|
||||
this.closeButton.hide();
|
||||
this.addButton.hide();
|
||||
$(this.removeButtons.selector).css('visibility', 'hidden');
|
||||
// hide review rules
|
||||
reviewersController.hideReviewRules()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* OnLine presence using channelstream
|
||||
*/
|
||||
window.ReviewerPresenceController = function (channel) {
|
||||
var self = this;
|
||||
this.channel = channel;
|
||||
this.users = {};
|
||||
|
||||
this.storeUsers = function (users) {
|
||||
self.users = {}
|
||||
$.each(users, function (index, value) {
|
||||
var userId = value.state.id;
|
||||
self.users[userId] = value.state;
|
||||
})
|
||||
}
|
||||
|
||||
this.render = function () {
|
||||
$.each($('.reviewer_entry'), function (index, value) {
|
||||
var userData = $(value).data();
|
||||
if (self.users[userData.reviewerUserId] !== undefined) {
|
||||
$(value).find('.presence-state').show();
|
||||
} else {
|
||||
$(value).find('.presence-state').hide();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
this.handlePresence = function (data) {
|
||||
if (data.type == 'presence' && data.channel === self.channel) {
|
||||
this.storeUsers(data.users);
|
||||
this.render()
|
||||
}
|
||||
};
|
||||
|
||||
this.handleChannelUpdate = function (data) {
|
||||
if (data.channel === this.channel) {
|
||||
this.storeUsers(data.state.users);
|
||||
this.render()
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* subscribe to the current presence */
|
||||
$.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
|
||||
/* subscribe to updates e.g connect/disconnect */
|
||||
$.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
|
||||
|
||||
};
|
||||
|
||||
window.refreshComments = function (version) {
|
||||
version = version || templateContext.pull_request_data.pull_request_version || '';
|
||||
|
||||
// Pull request case
|
||||
if (templateContext.pull_request_data.pull_request_id !== null) {
|
||||
var params = {
|
||||
'pull_request_id': templateContext.pull_request_data.pull_request_id,
|
||||
'repo_name': templateContext.repo_name,
|
||||
'version': version,
|
||||
};
|
||||
var loadUrl = pyroutes.url('pullrequest_comments', params);
|
||||
} // commit case
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
var currentIDs = []
|
||||
$.each($('.comment'), function (idx, element) {
|
||||
currentIDs.push($(element).data('commentId'));
|
||||
});
|
||||
var data = {"comments[]": currentIDs};
|
||||
|
||||
var $targetElem = $('.comments-content-table');
|
||||
$targetElem.css('opacity', 0.3);
|
||||
$targetElem.load(
|
||||
loadUrl, data, function (responseText, textStatus, jqXHR) {
|
||||
if (jqXHR.status !== 200) {
|
||||
return false;
|
||||
}
|
||||
var $counterElem = $('#comments-count');
|
||||
var newCount = $(responseText).data('counter');
|
||||
if (newCount !== undefined) {
|
||||
var callback = function () {
|
||||
$counterElem.animate({'opacity': 1.00}, 200)
|
||||
$counterElem.html(newCount);
|
||||
};
|
||||
$counterElem.animate({'opacity': 0.15}, 200, callback);
|
||||
}
|
||||
|
||||
$targetElem.css('opacity', 1);
|
||||
tooltipActivate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
window.refreshTODOs = function (version) {
|
||||
version = version || templateContext.pull_request_data.pull_request_version || '';
|
||||
// Pull request case
|
||||
if (templateContext.pull_request_data.pull_request_id !== null) {
|
||||
var params = {
|
||||
'pull_request_id': templateContext.pull_request_data.pull_request_id,
|
||||
'repo_name': templateContext.repo_name,
|
||||
'version': version,
|
||||
};
|
||||
var loadUrl = pyroutes.url('pullrequest_comments', params);
|
||||
} // commit case
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
var currentIDs = []
|
||||
$.each($('.comment'), function (idx, element) {
|
||||
currentIDs.push($(element).data('commentId'));
|
||||
});
|
||||
|
||||
var data = {"comments[]": currentIDs};
|
||||
var $targetElem = $('.todos-content-table');
|
||||
$targetElem.css('opacity', 0.3);
|
||||
$targetElem.load(
|
||||
loadUrl, data, function (responseText, textStatus, jqXHR) {
|
||||
if (jqXHR.status !== 200) {
|
||||
return false;
|
||||
}
|
||||
var $counterElem = $('#todos-count')
|
||||
var newCount = $(responseText).data('counter');
|
||||
if (newCount !== undefined) {
|
||||
var callback = function () {
|
||||
$counterElem.animate({'opacity': 1.00}, 200)
|
||||
$counterElem.html(newCount);
|
||||
};
|
||||
$counterElem.animate({'opacity': 0.15}, 200, callback);
|
||||
}
|
||||
|
||||
$targetElem.css('opacity', 1);
|
||||
tooltipActivate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
window.refreshAllComments = function (version) {
|
||||
version = version || templateContext.pull_request_data.pull_request_version || '';
|
||||
|
||||
refreshComments(version);
|
||||
refreshTODOs(version);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
import io
|
||||
import math
|
||||
import re
|
||||
import os
|
||||
import datetime
|
||||
|
|
@ -196,6 +197,72 @@ def write_metadata_if_needed(event):
|
|||
pass
|
||||
|
||||
|
||||
def write_usage_data(event):
|
||||
import rhodecode
|
||||
from rhodecode.lib import system_info
|
||||
from rhodecode.lib import ext_json
|
||||
|
||||
settings = event.app.registry.settings
|
||||
instance_tag = settings.get('metadata.write_usage_tag')
|
||||
if not settings.get('metadata.write_usage'):
|
||||
return
|
||||
|
||||
def get_update_age(dest_file):
|
||||
now = datetime.datetime.utcnow()
|
||||
|
||||
with open(dest_file, 'rb') as f:
|
||||
data = ext_json.json.loads(f.read())
|
||||
if 'created_on' in data:
|
||||
update_date = parse(data['created_on'])
|
||||
diff = now - update_date
|
||||
return math.ceil(diff.total_seconds() / 60.0)
|
||||
|
||||
return 0
|
||||
|
||||
utc_date = datetime.datetime.utcnow()
|
||||
hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
|
||||
fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
|
||||
date=utc_date, hour=hour_quarter)
|
||||
ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
|
||||
|
||||
usage_dir = os.path.join(ini_loc, '.rcusage')
|
||||
if not os.path.isdir(usage_dir):
|
||||
os.makedirs(usage_dir)
|
||||
usage_metadata_destination = os.path.join(usage_dir, fname)
|
||||
|
||||
try:
|
||||
age_in_min = get_update_age(usage_metadata_destination)
|
||||
except Exception:
|
||||
age_in_min = 0
|
||||
|
||||
# write every 6th hour
|
||||
if age_in_min and age_in_min < 60 * 6:
|
||||
log.debug('Usage file created %s minutes ago, skipping (threashold: %s)...',
|
||||
age_in_min, 60 * 6)
|
||||
return
|
||||
|
||||
def write(dest_file):
|
||||
configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
|
||||
license_token = configuration['config']['license_token']
|
||||
|
||||
metadata = dict(
|
||||
desc='Usage data',
|
||||
instance_tag=instance_tag,
|
||||
license_token=license_token,
|
||||
created_on=datetime.datetime.utcnow().isoformat(),
|
||||
usage=system_info.SysInfo(system_info.usage_info)()['value'],
|
||||
)
|
||||
|
||||
with open(dest_file, 'wb') as f:
|
||||
f.write(ext_json.json.dumps(metadata, indent=2, sort_keys=True))
|
||||
|
||||
try:
|
||||
log.debug('Writing usage file at: %s', usage_metadata_destination)
|
||||
write(usage_metadata_destination)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def write_js_routes_if_enabled(event):
|
||||
registry = event.app.registry
|
||||
|
||||
|
|
|
|||
|
|
@ -38,10 +38,12 @@
|
|||
<div class="main">
|
||||
${next.main()}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div id="footer">
|
||||
<div id="footer-inner" class="title wrapper">
|
||||
|
|
@ -699,9 +701,6 @@
|
|||
notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
|
||||
notice_display = 'none' if len(notice_messages) == 0 else ''
|
||||
%>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
<ul id="quick" class="main_nav navigation horizontal-list">
|
||||
## notice box for important system messages
|
||||
|
|
@ -1200,6 +1199,7 @@
|
|||
('g p', 'Goto pull requests page'),
|
||||
('g o', 'Goto repository settings'),
|
||||
('g O', 'Goto repository access permissions settings'),
|
||||
('t s', 'Toggle sidebar on some pages'),
|
||||
]
|
||||
%>
|
||||
%for key, desc in elems:
|
||||
|
|
@ -1219,3 +1219,36 @@
|
|||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
"use sctrict";
|
||||
|
||||
var $sideBar = $('.right-sidebar');
|
||||
var expanded = $sideBar.hasClass('right-sidebar-expanded');
|
||||
var sidebarState = templateContext.session_attrs.sidebarState;
|
||||
var sidebarEnabled = $('aside.right-sidebar').get(0);
|
||||
|
||||
if (sidebarState === 'expanded') {
|
||||
expanded = true
|
||||
} else if (sidebarState === 'collapsed') {
|
||||
expanded = false
|
||||
}
|
||||
if (sidebarEnabled) {
|
||||
// show sidebar since it's hidden on load
|
||||
$('.right-sidebar').show();
|
||||
|
||||
// init based on set initial class, or if defined user session attrs
|
||||
if (expanded) {
|
||||
window.expandSidebar();
|
||||
window.updateStickyHeader();
|
||||
|
||||
} else {
|
||||
window.collapseSidebar();
|
||||
window.updateStickyHeader();
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
</script>
|
||||
|
|
|
|||
142
rhodecode/templates/base/sidebar.mako
Normal file
142
rhodecode/templates/base/sidebar.mako
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
## snippet for sidebar elements
|
||||
## usage:
|
||||
## <%namespace name="sidebar" file="/base/sidebar.mako"/>
|
||||
## ${sidebar.comments_table()}
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
|
||||
<%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)">
|
||||
<%
|
||||
if todo_comments:
|
||||
cls_ = 'todos-content-table'
|
||||
def sorter(entry):
|
||||
user_id = entry.author.user_id
|
||||
resolved = '1' if entry.resolved else '0'
|
||||
if user_id == c.rhodecode_user.user_id:
|
||||
# own comments first
|
||||
user_id = 0
|
||||
return '{}'.format(str(entry.comment_id).zfill(10000))
|
||||
else:
|
||||
cls_ = 'comments-content-table'
|
||||
def sorter(entry):
|
||||
user_id = entry.author.user_id
|
||||
return '{}'.format(str(entry.comment_id).zfill(10000))
|
||||
|
||||
existing_ids = existing_ids or []
|
||||
|
||||
%>
|
||||
|
||||
<table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}">
|
||||
|
||||
% for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))):
|
||||
<%
|
||||
display = ''
|
||||
_cls = ''
|
||||
%>
|
||||
|
||||
<%
|
||||
comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', []))
|
||||
prev_comment_ver_index = 0
|
||||
if loop_obj.previous:
|
||||
prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', []))
|
||||
|
||||
ver_info = None
|
||||
if getattr(c, 'versions', []):
|
||||
ver_info = c.versions[comment_ver_index-1] if comment_ver_index else None
|
||||
%>
|
||||
<% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %>
|
||||
<% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %>
|
||||
<%
|
||||
if (prev_comment_ver_index > comment_ver_index):
|
||||
comments_ver_divider = comment_ver_index
|
||||
else:
|
||||
comments_ver_divider = None
|
||||
%>
|
||||
|
||||
% if todo_comments:
|
||||
% if comment_obj.resolved:
|
||||
<% _cls = 'resolved-todo' %>
|
||||
<% display = 'none' %>
|
||||
% endif
|
||||
% else:
|
||||
## SKIP TODOs we display them in other area
|
||||
% if comment_obj.is_todo:
|
||||
<% display = 'none' %>
|
||||
% endif
|
||||
## Skip outdated comments
|
||||
% if comment_obj.outdated:
|
||||
<% display = 'none' %>
|
||||
<% _cls = 'hidden-comment' %>
|
||||
% endif
|
||||
% endif
|
||||
|
||||
% if not todo_comments and comments_ver_divider:
|
||||
<tr class="old-comments-marker">
|
||||
<td colspan="3">
|
||||
% if ver_info:
|
||||
<code>v${comments_ver_divider} ${h.age_component(ver_info.created_on, time_is_local=True, tooltip=False)}</code>
|
||||
% else:
|
||||
<code>v${comments_ver_divider}</code>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
% endif
|
||||
|
||||
<tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}">
|
||||
<td class="td-todo-number">
|
||||
<%
|
||||
version_info = ''
|
||||
if is_pr:
|
||||
version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version')
|
||||
%>
|
||||
|
||||
<script type="text/javascript">
|
||||
// closure function helper
|
||||
var sidebarComment${comment_obj.comment_id} = function() {
|
||||
return renderTemplate('sideBarCommentHovercard', {
|
||||
version_info: "${version_info}",
|
||||
file_name: "${comment_obj.f_path}",
|
||||
line_no: "${comment_obj.line_no}",
|
||||
outdated: ${h.json.dumps(comment_obj.outdated)},
|
||||
inline: ${h.json.dumps(comment_obj.is_inline)},
|
||||
is_todo: ${h.json.dumps(comment_obj.is_todo)},
|
||||
created_on: "${h.format_date(comment_obj.created_on)}",
|
||||
datetime: "${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}",
|
||||
review_status: "${(comment_obj.review_status or '')}"
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
% if comment_obj.outdated:
|
||||
<i class="icon-comment-toggle tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i>
|
||||
% elif comment_obj.is_inline:
|
||||
<i class="icon-code tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i>
|
||||
% else:
|
||||
<i class="icon-comment tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i>
|
||||
% endif
|
||||
|
||||
## NEW, since refresh
|
||||
% if existing_ids and comment_obj.comment_id not in existing_ids:
|
||||
<span class="tag">NEW</span>
|
||||
% endif
|
||||
</td>
|
||||
|
||||
<td class="td-todo-gravatar">
|
||||
${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])}
|
||||
</td>
|
||||
<td class="todo-comment-text-wrapper">
|
||||
<div class="todo-comment-text ${('todo-resolved' if comment_obj.resolved else '')}">
|
||||
<a class="${('todo-resolved' if comment_obj.resolved else '')} permalink"
|
||||
href="#comment-${comment_obj.comment_id}"
|
||||
onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})">
|
||||
|
||||
${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
|
||||
</table>
|
||||
|
||||
</%def>
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
<%namespace name="base" file="/base/base.mako"/>
|
||||
<%namespace name="diff_block" file="/changeset/diff_block.mako"/>
|
||||
<%namespace name="file_base" file="/files/base.mako"/>
|
||||
<%namespace name="sidebar" file="/base/sidebar.mako"/>
|
||||
|
||||
|
||||
<%def name="title()">
|
||||
${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
|
||||
|
|
@ -100,22 +102,6 @@
|
|||
% endif
|
||||
</div>
|
||||
|
||||
%if c.statuses:
|
||||
<div class="tag status-tag-${c.statuses[0]} pull-right">
|
||||
<i class="icon-circle review-status-${c.statuses[0]}"></i>
|
||||
<div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div>
|
||||
</div>
|
||||
%endif
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
|
||||
<div class="left-label-summary">
|
||||
<p>${_('Commit navigation')}:</p>
|
||||
<div class="right-label-summary">
|
||||
<span id="parent_link" class="tag tagtag">
|
||||
<a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
|
||||
</span>
|
||||
|
|
@ -123,7 +109,9 @@
|
|||
<span id="child_link" class="tag tagtag">
|
||||
<a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -160,7 +148,9 @@
|
|||
<%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
|
||||
${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
|
||||
${cbdiffs.render_diffset(
|
||||
c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
|
||||
c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,
|
||||
inline_comments=c.inline_comments,
|
||||
show_todos=False)}
|
||||
</div>
|
||||
|
||||
## template for inline comment form
|
||||
|
|
@ -169,7 +159,7 @@
|
|||
## comments heading with count
|
||||
<div class="comments-heading">
|
||||
<i class="icon-comment"></i>
|
||||
${_('Comments')} ${len(c.comments)}
|
||||
${_('General Comments')} ${len(c.comments)}
|
||||
</div>
|
||||
|
||||
## render comments
|
||||
|
|
@ -180,123 +170,262 @@
|
|||
h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
|
||||
</div>
|
||||
|
||||
## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
|
||||
<script type="text/javascript">
|
||||
### NAV SIDEBAR
|
||||
<aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none">
|
||||
<div class="sidenav navbar__inner" >
|
||||
## TOGGLE
|
||||
<div class="sidebar-toggle" onclick="toggleSidebar(); return false">
|
||||
<a href="#toggleSidebar" class="grey-link-action">
|
||||
|
||||
$(document).ready(function() {
|
||||
</a>
|
||||
</div>
|
||||
|
||||
var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
|
||||
if($('#trimmed_message_box').height() === boxmax){
|
||||
$('#message_expand').show();
|
||||
}
|
||||
## CONTENT
|
||||
<div class="sidebar-content">
|
||||
|
||||
$('#message_expand').on('click', function(e){
|
||||
$('#trimmed_message_box').css('max-height', 'none');
|
||||
$(this).hide();
|
||||
});
|
||||
## RULES SUMMARY/RULES
|
||||
<div class="sidebar-element clear-both">
|
||||
<% vote_title = _ungettext(
|
||||
'Status calculated based on votes from {} reviewer',
|
||||
'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))
|
||||
%>
|
||||
|
||||
$('.show-inline-comments').on('click', function(e){
|
||||
var boxid = $(this).attr('data-comment-id');
|
||||
var button = $(this);
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
|
||||
<i class="icon-circle review-status-${c.commit_review_status}"></i>
|
||||
${len(c.allowed_reviewers)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if(button.hasClass("comments-visible")) {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function(index){
|
||||
$(this).hide();
|
||||
## REVIEWERS
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
<span class="tooltip sidebar-heading" title="${vote_title}">
|
||||
<i class="icon-circle review-status-${c.commit_review_status}"></i>
|
||||
${_('Reviewers')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
|
||||
|
||||
<table id="review_members" class="group_members">
|
||||
## This content is loaded via JS and ReviewersPanel
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
## TODOs
|
||||
<div class="sidebar-element clear-both">
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
|
||||
<i class="icon-flag-filled"></i>
|
||||
<span id="todos-count">${len(c.unresolved_comments)}</span>
|
||||
</div>
|
||||
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
## Only show unresolved, that is only what matters
|
||||
<span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
|
||||
<i class="icon-flag-filled"></i>
|
||||
TODOs
|
||||
</span>
|
||||
|
||||
% if c.resolved_comments:
|
||||
<span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
|
||||
% else:
|
||||
<span class="block-right last-item noselect">Show resolved</span>
|
||||
% endif
|
||||
|
||||
</div>
|
||||
|
||||
<div class="right-sidebar-expanded-state pr-details-content">
|
||||
% if c.unresolved_comments + c.resolved_comments:
|
||||
${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
|
||||
% else:
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
${_('No TODOs yet')}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## COMMENTS
|
||||
<div class="sidebar-element clear-both">
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
|
||||
<i class="icon-comment" style="color: #949494"></i>
|
||||
<span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
|
||||
<span class="display-none" id="general-comments-count">${len(c.comments)}</span>
|
||||
<span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
|
||||
</div>
|
||||
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
<span class="sidebar-heading noselect" onclick="refreshComments(); return false">
|
||||
<i class="icon-comment" style="color: #949494"></i>
|
||||
${_('Comments')}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="right-sidebar-expanded-state pr-details-content">
|
||||
% if c.inline_comments_flat + c.comments:
|
||||
${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)}
|
||||
% else:
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
${_('No Comments yet')}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
|
||||
<script type="text/javascript">
|
||||
window.setReviewersData = ${c.commit_set_reviewers_data_json | n};
|
||||
|
||||
$(document).ready(function () {
|
||||
var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
|
||||
|
||||
if ($('#trimmed_message_box').height() === boxmax) {
|
||||
$('#message_expand').show();
|
||||
}
|
||||
|
||||
$('#message_expand').on('click', function (e) {
|
||||
$('#trimmed_message_box').css('max-height', 'none');
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
$('.show-inline-comments').on('click', function (e) {
|
||||
var boxid = $(this).attr('data-comment-id');
|
||||
var button = $(this);
|
||||
|
||||
if (button.hasClass("comments-visible")) {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function (index) {
|
||||
$(this).hide();
|
||||
});
|
||||
button.removeClass("comments-visible");
|
||||
} else {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function(index){
|
||||
$(this).show();
|
||||
} else {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function (index) {
|
||||
$(this).show();
|
||||
});
|
||||
button.addClass("comments-visible");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// next links
|
||||
$('#child_link').on('click', function(e){
|
||||
// fetch via ajax what is going to be the next link, if we have
|
||||
// >1 links show them to user to choose
|
||||
if(!$('#child_link').hasClass('disabled')){
|
||||
$.ajax({
|
||||
// next links
|
||||
$('#child_link').on('click', function (e) {
|
||||
// fetch via ajax what is going to be the next link, if we have
|
||||
// >1 links show them to user to choose
|
||||
if (!$('#child_link').hasClass('disabled')) {
|
||||
$.ajax({
|
||||
url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
|
||||
success: function(data) {
|
||||
if(data.results.length === 0){
|
||||
$('#child_link').html("${_('No Child Commits')}").addClass('disabled');
|
||||
}
|
||||
if(data.results.length === 1){
|
||||
var commit = data.results[0];
|
||||
window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
|
||||
}
|
||||
else if(data.results.length === 2){
|
||||
$('#child_link').addClass('disabled');
|
||||
$('#child_link').addClass('double');
|
||||
success: function (data) {
|
||||
if (data.results.length === 0) {
|
||||
$('#child_link').html("${_('No Child Commits')}").addClass('disabled');
|
||||
}
|
||||
if (data.results.length === 1) {
|
||||
var commit = data.results[0];
|
||||
window.location = pyroutes.url('repo_commit', {
|
||||
'repo_name': '${c.repo_name}',
|
||||
'commit_id': commit.raw_id
|
||||
});
|
||||
} else if (data.results.length === 2) {
|
||||
$('#child_link').addClass('disabled');
|
||||
$('#child_link').addClass('double');
|
||||
|
||||
var _html = '';
|
||||
_html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
|
||||
.replace('__branch__', data.results[0].branch)
|
||||
.replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
|
||||
.replace('__title__', data.results[0].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
|
||||
_html +=' | ';
|
||||
_html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
|
||||
.replace('__branch__', data.results[1].branch)
|
||||
.replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
|
||||
.replace('__title__', data.results[1].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
|
||||
$('#child_link').html(_html);
|
||||
}
|
||||
var _html = '';
|
||||
_html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
|
||||
.replace('__branch__', data.results[0].branch)
|
||||
.replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
|
||||
.replace('__title__', data.results[0].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {
|
||||
'repo_name': '${c.repo_name}',
|
||||
'commit_id': data.results[0].raw_id
|
||||
}));
|
||||
_html += ' | ';
|
||||
_html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
|
||||
.replace('__branch__', data.results[1].branch)
|
||||
.replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
|
||||
.replace('__title__', data.results[1].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {
|
||||
'repo_name': '${c.repo_name}',
|
||||
'commit_id': data.results[1].raw_id
|
||||
}));
|
||||
$('#child_link').html(_html);
|
||||
}
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// prev links
|
||||
$('#parent_link').on('click', function(e){
|
||||
// fetch via ajax what is going to be the next link, if we have
|
||||
// >1 links show them to user to choose
|
||||
if(!$('#parent_link').hasClass('disabled')){
|
||||
$.ajax({
|
||||
// prev links
|
||||
$('#parent_link').on('click', function (e) {
|
||||
// fetch via ajax what is going to be the next link, if we have
|
||||
// >1 links show them to user to choose
|
||||
if (!$('#parent_link').hasClass('disabled')) {
|
||||
$.ajax({
|
||||
url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
|
||||
success: function(data) {
|
||||
if(data.results.length === 0){
|
||||
$('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
|
||||
}
|
||||
if(data.results.length === 1){
|
||||
var commit = data.results[0];
|
||||
window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
|
||||
}
|
||||
else if(data.results.length === 2){
|
||||
$('#parent_link').addClass('disabled');
|
||||
$('#parent_link').addClass('double');
|
||||
success: function (data) {
|
||||
if (data.results.length === 0) {
|
||||
$('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
|
||||
}
|
||||
if (data.results.length === 1) {
|
||||
var commit = data.results[0];
|
||||
window.location = pyroutes.url('repo_commit', {
|
||||
'repo_name': '${c.repo_name}',
|
||||
'commit_id': commit.raw_id
|
||||
});
|
||||
} else if (data.results.length === 2) {
|
||||
$('#parent_link').addClass('disabled');
|
||||
$('#parent_link').addClass('double');
|
||||
|
||||
var _html = '';
|
||||
_html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
|
||||
.replace('__branch__', data.results[0].branch)
|
||||
.replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
|
||||
.replace('__title__', data.results[0].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
|
||||
_html +=' | ';
|
||||
_html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
|
||||
.replace('__branch__', data.results[1].branch)
|
||||
.replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
|
||||
.replace('__title__', data.results[1].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
|
||||
$('#parent_link').html(_html);
|
||||
}
|
||||
var _html = '';
|
||||
_html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
|
||||
.replace('__branch__', data.results[0].branch)
|
||||
.replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
|
||||
.replace('__title__', data.results[0].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {
|
||||
'repo_name': '${c.repo_name}',
|
||||
'commit_id': data.results[0].raw_id
|
||||
}));
|
||||
_html += ' | ';
|
||||
_html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
|
||||
.replace('__branch__', data.results[1].branch)
|
||||
.replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
|
||||
.replace('__title__', data.results[1].message)
|
||||
.replace('__url__', pyroutes.url('repo_commit', {
|
||||
'repo_name': '${c.repo_name}',
|
||||
'commit_id': data.results[1].raw_id
|
||||
}));
|
||||
$('#parent_link').html(_html);
|
||||
}
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// browse tree @ revision
|
||||
$('#files_link').on('click', function(e){
|
||||
window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
|
||||
e.preventDefault();
|
||||
});
|
||||
// browse tree @ revision
|
||||
$('#files_link').on('click', function (e) {
|
||||
window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
ReviewersPanel.init(null, setReviewersData);
|
||||
|
||||
var channel = '${c.commit_broadcast_channel}';
|
||||
new ReviewerPresenceController(channel)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
</%def>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,18 @@
|
|||
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
<%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
|
||||
<% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
|
||||
|
||||
<%
|
||||
from rhodecode.model.comment import CommentsModel
|
||||
comment_model = CommentsModel()
|
||||
%>
|
||||
<% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
|
||||
<% latest_ver = len(getattr(c, 'versions', [])) %>
|
||||
|
||||
% if inline:
|
||||
<% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
|
||||
<% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
|
||||
% else:
|
||||
<% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
|
||||
<% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
|
||||
% endif
|
||||
|
||||
<div class="comment
|
||||
|
|
@ -70,9 +76,9 @@
|
|||
status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id))
|
||||
%>
|
||||
|
||||
<i class="icon-circle review-status-${comment.status_change[0].status}"></i>
|
||||
<i class="icon-circle review-status-${comment.review_status}"></i>
|
||||
<div class="changeset-status-lbl tooltip" title="${status_change_title}">
|
||||
${comment.status_change[0].status_lbl}
|
||||
${comment.review_status_lbl}
|
||||
</div>
|
||||
% else:
|
||||
<div>
|
||||
|
|
@ -153,69 +159,90 @@
|
|||
</div>
|
||||
%endif
|
||||
|
||||
<a class="permalink" href="#comment-${comment.comment_id}"> ¶</a>
|
||||
|
||||
<div class="comment-links-block">
|
||||
|
||||
% if inline:
|
||||
<a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
|
||||
% if outdated_at_ver:
|
||||
<code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
|
||||
outdated ${'v{}'.format(pr_index_ver)} |
|
||||
</code>
|
||||
% elif pr_index_ver:
|
||||
<code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
|
||||
${'v{}'.format(pr_index_ver)} |
|
||||
</code>
|
||||
<code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">outdated ${'v{}'.format(comment_ver)}</code>
|
||||
<code class="action-divider">|</code>
|
||||
% elif comment_ver:
|
||||
<code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
|
||||
<code class="action-divider">|</code>
|
||||
% endif
|
||||
</a>
|
||||
% else:
|
||||
% if pr_index_ver:
|
||||
% if comment_ver:
|
||||
|
||||
% if comment.outdated:
|
||||
<a class="pr-version"
|
||||
href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
|
||||
>
|
||||
${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
|
||||
</a> |
|
||||
${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
|
||||
</a>
|
||||
<code class="action-divider">|</code>
|
||||
% else:
|
||||
<a class="tooltip pr-version"
|
||||
title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"
|
||||
title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
|
||||
href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"
|
||||
>
|
||||
<code class="pr-version-num">
|
||||
${'v{}'.format(pr_index_ver)}
|
||||
</code>
|
||||
</a> |
|
||||
<code class="pr-version-num">${'v{}'.format(comment_ver)}</code>
|
||||
</a>
|
||||
<code class="action-divider">|</code>
|
||||
% endif
|
||||
|
||||
% endif
|
||||
% endif
|
||||
|
||||
## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
|
||||
## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
|
||||
%if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
|
||||
## permissions to delete
|
||||
%if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
|
||||
<a onclick="return Rhodecode.comments.editComment(this);"
|
||||
class="edit-comment">${_('Edit')}</a>
|
||||
| <a onclick="return Rhodecode.comments.deleteComment(this);"
|
||||
class="delete-comment">${_('Delete')}</a>
|
||||
%else:
|
||||
<a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
|
||||
| <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
|
||||
%endif
|
||||
%else:
|
||||
<a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
|
||||
| <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
|
||||
%endif
|
||||
<details class="details-reset details-inline-block">
|
||||
<summary class="noselect"><i class="icon-options cursor-pointer"></i></summary>
|
||||
<details-menu class="details-dropdown">
|
||||
|
||||
<div class="dropdown-item">
|
||||
${_('Comment')} #${comment.comment_id}
|
||||
<span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${comment_model.get_url(comment,request, permalink=True, anchor='comment-{}'.format(comment.comment_id))}" title="${_('Copy permalink')}"></span>
|
||||
</div>
|
||||
|
||||
## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
|
||||
## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
|
||||
%if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
|
||||
## permissions to delete
|
||||
%if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="dropdown-item">
|
||||
<a onclick="return Rhodecode.comments.editComment(this);" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
|
||||
</div>
|
||||
<div class="dropdown-item">
|
||||
<a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
|
||||
</div>
|
||||
%else:
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="dropdown-item">
|
||||
<a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
|
||||
</div>
|
||||
<div class="dropdown-item">
|
||||
<a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
|
||||
</div>
|
||||
%endif
|
||||
%else:
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="dropdown-item">
|
||||
<a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
|
||||
</div>
|
||||
<div class="dropdown-item">
|
||||
<a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
|
||||
</div>
|
||||
%endif
|
||||
</details-menu>
|
||||
</details>
|
||||
|
||||
<code class="action-divider">|</code>
|
||||
% if outdated_at_ver:
|
||||
| <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
|
||||
| <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
|
||||
<a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
|
||||
<a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
|
||||
% else:
|
||||
| <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
|
||||
| <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
|
||||
<a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
|
||||
<a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
|
||||
% endif
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -102,6 +102,11 @@
|
|||
<%namespace name="diff_block" file="/changeset/diff_block.mako"/>
|
||||
|
||||
%for commit in c.commit_ranges:
|
||||
## commit range header for each individual diff
|
||||
<h3>
|
||||
<a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
|
||||
</h3>
|
||||
|
||||
${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])}
|
||||
${cbdiffs.render_diffset(
|
||||
diffset=c.changes[commit.raw_id],
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
|
|||
diffset_container_id = h.md5(diffset.target_ref)
|
||||
collapse_all = len(diffset.files) > collapse_when_files_over
|
||||
active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
|
||||
from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
|
||||
MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
|
||||
%>
|
||||
|
||||
%if use_comments:
|
||||
|
|
@ -159,45 +161,45 @@ return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
|
|||
</div>
|
||||
% endif
|
||||
|
||||
## comments
|
||||
<div class="pull-right">
|
||||
<div class="comments-number" style="padding-left: 10px">
|
||||
% if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
|
||||
<i class="icon-comment" style="color: #949494">COMMENTS:</i>
|
||||
% if c.comments:
|
||||
<a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
|
||||
% else:
|
||||
${_('0 General')}
|
||||
% endif
|
||||
|
||||
% if c.inline_cnt:
|
||||
<a href="#" onclick="return Rhodecode.comments.nextComment();"
|
||||
id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
|
||||
</a>
|
||||
% else:
|
||||
${_('0 Inline')}
|
||||
% endif
|
||||
% endif
|
||||
|
||||
% if pull_request_menu:
|
||||
<%
|
||||
outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
|
||||
%>
|
||||
|
||||
% if outdated_comm_count_ver:
|
||||
<a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
|
||||
(${_("{} Outdated").format(outdated_comm_count_ver)})
|
||||
</a>
|
||||
<a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
|
||||
<a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
|
||||
% else:
|
||||
(${_("{} Outdated").format(outdated_comm_count_ver)})
|
||||
% endif
|
||||
|
||||
% endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
## ## comments
|
||||
## <div class="pull-right">
|
||||
## <div class="comments-number" style="padding-left: 10px">
|
||||
## % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
|
||||
## <i class="icon-comment" style="color: #949494">COMMENTS:</i>
|
||||
## % if c.comments:
|
||||
## <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
|
||||
## % else:
|
||||
## ${_('0 General')}
|
||||
## % endif
|
||||
##
|
||||
## % if c.inline_cnt:
|
||||
## <a href="#" onclick="return Rhodecode.comments.nextComment();"
|
||||
## id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
|
||||
## </a>
|
||||
## % else:
|
||||
## ${_('0 Inline')}
|
||||
## % endif
|
||||
## % endif
|
||||
##
|
||||
## % if pull_request_menu:
|
||||
## <%
|
||||
## outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
|
||||
## %>
|
||||
##
|
||||
## % if outdated_comm_count_ver:
|
||||
## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
|
||||
## (${_("{} Outdated").format(outdated_comm_count_ver)})
|
||||
## </a>
|
||||
## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
|
||||
## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
|
||||
## % else:
|
||||
## (${_("{} Outdated").format(outdated_comm_count_ver)})
|
||||
## % endif
|
||||
##
|
||||
## % endif
|
||||
##
|
||||
## </div>
|
||||
## </div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -208,13 +210,6 @@ return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
|
|||
<a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
|
||||
</h2>
|
||||
</div>
|
||||
## commit range header for each individual diff
|
||||
% elif commit and hasattr(c, 'commit_ranges') and len(c.commit_ranges) > 1:
|
||||
<div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
|
||||
<div class="clearinner">
|
||||
<a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=diffset.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<div id="todo-box">
|
||||
|
|
@ -239,6 +234,43 @@ return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
|
|||
<% over_lines_changed_limit = False %>
|
||||
%for i, filediff in enumerate(diffset.files):
|
||||
|
||||
%if filediff.source_file_path and filediff.target_file_path:
|
||||
%if filediff.source_file_path != filediff.target_file_path:
|
||||
## file was renamed, or copied
|
||||
%if RENAMED_FILENODE in filediff.patch['stats']['ops']:
|
||||
<%
|
||||
final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> <del>{}</del>'.format(filediff.target_file_path, filediff.source_file_path))
|
||||
final_path = filediff.target_file_path
|
||||
%>
|
||||
%elif COPIED_FILENODE in filediff.patch['stats']['ops']:
|
||||
<%
|
||||
final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> {}'.format(filediff.target_file_path, filediff.source_file_path))
|
||||
final_path = filediff.target_file_path
|
||||
%>
|
||||
%endif
|
||||
%else:
|
||||
## file was modified
|
||||
<%
|
||||
final_file_name = filediff.source_file_path
|
||||
final_path = final_file_name
|
||||
%>
|
||||
%endif
|
||||
%else:
|
||||
%if filediff.source_file_path:
|
||||
## file was deleted
|
||||
<%
|
||||
final_file_name = filediff.source_file_path
|
||||
final_path = final_file_name
|
||||
%>
|
||||
%else:
|
||||
## file was added
|
||||
<%
|
||||
final_file_name = filediff.target_file_path
|
||||
final_path = final_file_name
|
||||
%>
|
||||
%endif
|
||||
%endif
|
||||
|
||||
<%
|
||||
lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
|
||||
over_lines_changed_limit = lines_changed > lines_changed_limit
|
||||
|
|
@ -258,13 +290,39 @@ return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
|
|||
total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated]
|
||||
%>
|
||||
<div class="filediff-collapse-indicator icon-"></div>
|
||||
<span class="pill-group pull-right" >
|
||||
<span class="pill" op="comments">
|
||||
|
||||
## Comments/Options PILL
|
||||
<span class="pill-group pull-right">
|
||||
<span class="pill" op="comments">
|
||||
<i class="icon-comment"></i> ${len(total_file_comments)}
|
||||
</span>
|
||||
|
||||
<details class="details-reset details-inline-block">
|
||||
<summary class="noselect">
|
||||
<i class="pill icon-options cursor-pointer" op="options"></i>
|
||||
</summary>
|
||||
<details-menu class="details-dropdown">
|
||||
|
||||
<div class="dropdown-item">
|
||||
<span>${final_path}</span>
|
||||
<span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="Copy file path"></span>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<div class="dropdown-item">
|
||||
<% permalink = request.current_route_url(_anchor='a_{}'.format(h.FID(filediff.raw_id, filediff.patch['filename']))) %>
|
||||
<a href="${permalink}">¶ permalink</a>
|
||||
<span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${permalink}" title="Copy permalink"></span>
|
||||
</div>
|
||||
|
||||
|
||||
</details-menu>
|
||||
</details>
|
||||
|
||||
</span>
|
||||
${diff_ops(filediff)}
|
||||
|
||||
${diff_ops(final_file_name, filediff)}
|
||||
|
||||
</label>
|
||||
|
||||
|
|
@ -463,43 +521,15 @@ return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
|
|||
</div>
|
||||
</%def>
|
||||
|
||||
<%def name="diff_ops(filediff)">
|
||||
<%
|
||||
from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
|
||||
MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
|
||||
%>
|
||||
<%def name="diff_ops(file_name, filediff)">
|
||||
<%
|
||||
from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
|
||||
MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
|
||||
%>
|
||||
<span class="pill">
|
||||
<i class="icon-file-text"></i>
|
||||
%if filediff.source_file_path and filediff.target_file_path:
|
||||
%if filediff.source_file_path != filediff.target_file_path:
|
||||
## file was renamed, or copied
|
||||
%if RENAMED_FILENODE in filediff.patch['stats']['ops']:
|
||||
${filediff.target_file_path} ⬅ <del>${filediff.source_file_path}</del>
|
||||
<% final_path = filediff.target_file_path %>
|
||||
%elif COPIED_FILENODE in filediff.patch['stats']['ops']:
|
||||
${filediff.target_file_path} ⬅ ${filediff.source_file_path}
|
||||
<% final_path = filediff.target_file_path %>
|
||||
%endif
|
||||
%else:
|
||||
## file was modified
|
||||
${filediff.source_file_path}
|
||||
<% final_path = filediff.source_file_path %>
|
||||
%endif
|
||||
%else:
|
||||
%if filediff.source_file_path:
|
||||
## file was deleted
|
||||
${filediff.source_file_path}
|
||||
<% final_path = filediff.source_file_path %>
|
||||
%else:
|
||||
## file was added
|
||||
${filediff.target_file_path}
|
||||
<% final_path = filediff.target_file_path %>
|
||||
%endif
|
||||
%endif
|
||||
<i style="color: #aaa" class="on-hover-icon icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="${_('Copy file path')}" onclick="return false;"></i>
|
||||
${file_name}
|
||||
</span>
|
||||
## anchor link
|
||||
<a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filediff.patch['filename'])}">¶</a>
|
||||
|
||||
<span class="pill-group pull-right">
|
||||
|
||||
|
|
@ -934,7 +964,7 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
</span>
|
||||
%endif
|
||||
% if commit or pull_request_menu:
|
||||
<span id="diff_nav">Loading diff...:</span>
|
||||
<span class="tooltip" title="Navigate to previous or next change inside files." id="diff_nav">Loading diff...:</span>
|
||||
<span class="cursor-pointer" onclick="scrollToPrevChunk(); return false">
|
||||
<i class="icon-angle-up"></i>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@
|
|||
## 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)
|
||||
urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns, issues_container=getattr(c, 'referenced_commit_issues', None))
|
||||
%>
|
||||
|
||||
%for commit in c.commit_ranges:
|
||||
<tr id="row-${commit.raw_id}"
|
||||
commit_id="${commit.raw_id}"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<%text>
|
||||
<div style="display: none">
|
||||
|
||||
<script>
|
||||
var CG = new ColorGenerator();
|
||||
</script>
|
||||
|
||||
<script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
|
||||
|
||||
<%
|
||||
|
|
@ -34,38 +38,41 @@ var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
|
|||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var CG = new ColorGenerator();
|
||||
</script>
|
||||
|
||||
<script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
|
||||
<%
|
||||
if (create) {
|
||||
var edit_visibility = 'visible';
|
||||
} else {
|
||||
var edit_visibility = 'hidden';
|
||||
}
|
||||
|
||||
<li id="reviewer_<%= member.user_id %>" class="reviewer_entry">
|
||||
<%
|
||||
if (create) {
|
||||
var edit_visibility = 'visible';
|
||||
} else {
|
||||
var edit_visibility = 'hidden';
|
||||
}
|
||||
if (member.user_group && member.user_group.vote_rule) {
|
||||
var reviewGroup = '<i class="icon-user-group"></i>';
|
||||
var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule));
|
||||
} else {
|
||||
var reviewGroup = null;
|
||||
var reviewGroupColor = 'transparent';
|
||||
}
|
||||
var rule_show = rule_show || false;
|
||||
|
||||
if (member.user_group && member.user_group.vote_rule) {
|
||||
var groupStyle = 'border-left: 1px solid '+CG.asRGB(CG.getColor(member.user_group.vote_rule));
|
||||
} else {
|
||||
var groupStyle = 'border-left: 1px solid white';
|
||||
}
|
||||
%>
|
||||
if (rule_show) {
|
||||
var rule_visibility = 'table-cell';
|
||||
} else {
|
||||
var rule_visibility = 'none';
|
||||
}
|
||||
|
||||
<div class="reviewers_member" style="<%= groupStyle%>" >
|
||||
%>
|
||||
|
||||
<tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
|
||||
|
||||
<td style="width: 20px">
|
||||
<div class="reviewer_status tooltip" title="<%= review_status_label %>">
|
||||
<i class="icon-circle review-status-<%= review_status %>"></i>
|
||||
</div>
|
||||
<div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
|
||||
<% if (mandatory) { %>
|
||||
<div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
|
||||
<i class="icon-lock"></i>
|
||||
</div>
|
||||
<% } %>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
|
||||
<%-
|
||||
renderTemplate('gravatarWithUser', {
|
||||
'size': 16,
|
||||
|
|
@ -77,12 +84,44 @@ var CG = new ColorGenerator();
|
|||
'gravatar_url': member.gravatar_link
|
||||
})
|
||||
%>
|
||||
<span class="tooltip presence-state" style="display: none" title="This users is currently at this page">
|
||||
<i class="icon-eye" style="color: #0ac878"></i>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style="width: 10px">
|
||||
<% if (reviewGroup !== null) { %>
|
||||
<span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>">
|
||||
<%- reviewGroup %>
|
||||
</span>
|
||||
<% } %>
|
||||
</td>
|
||||
|
||||
<% if (mandatory) { %>
|
||||
<td style="text-align: right;width: 10px;">
|
||||
<div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
|
||||
<i class="icon-lock"></i>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<% } else { %>
|
||||
<td style="text-align: right;width: 10px;">
|
||||
<% if (allowed_to_update) { %>
|
||||
<div class="reviewer_member_remove" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
|
||||
<i class="icon-remove"></i>
|
||||
</div>
|
||||
<% } %>
|
||||
</td>
|
||||
<% } %>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
|
||||
<input type="hidden" name="__start__" value="reviewer:mapping">
|
||||
|
||||
|
||||
<%if (member.user_group && member.user_group.vote_rule) {%>
|
||||
<%if (member.user_group && member.user_group.vote_rule) { %>
|
||||
<div class="reviewer_reason">
|
||||
|
||||
<%if (member.user_group.vote_rule == -1) {%>
|
||||
|
|
@ -91,7 +130,7 @@ var CG = new ColorGenerator();
|
|||
- group votes required: <%= member.user_group.vote_rule %>
|
||||
<%}%>
|
||||
</div>
|
||||
<%}%>
|
||||
<%} %>
|
||||
|
||||
<input type="hidden" name="__start__" value="reasons:sequence">
|
||||
<% for (var i = 0; i < reasons.length; i++) { %>
|
||||
|
|
@ -99,37 +138,24 @@ var CG = new ColorGenerator();
|
|||
<div class="reviewer_reason">- <%= reason %></div>
|
||||
<input type="hidden" name="reason" value="<%= reason %>">
|
||||
<% } %>
|
||||
<input type="hidden" name="__end__" value="reasons:sequence">
|
||||
<input type="hidden" name="__end__" value="reasons:sequence">
|
||||
|
||||
<input type="hidden" name="__start__" value="rules:sequence">
|
||||
<input type="hidden" name="__start__" value="rules:sequence">
|
||||
<% for (var i = 0; i < member.rules.length; i++) { %>
|
||||
<% var rule = member.rules[i] %>
|
||||
<input type="hidden" name="rule_id" value="<%= rule %>">
|
||||
<% } %>
|
||||
<input type="hidden" name="__end__" value="rules:sequence">
|
||||
<input type="hidden" name="__end__" value="rules:sequence">
|
||||
|
||||
<input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
|
||||
<input type="hidden" name="mandatory" value="<%= mandatory %>"/>
|
||||
<input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
|
||||
<input type="hidden" name="mandatory" value="<%= mandatory %>"/>
|
||||
|
||||
<input type="hidden" name="__end__" value="reviewer:mapping">
|
||||
|
||||
<% if (mandatory) { %>
|
||||
<div class="reviewer_member_mandatory_remove" style="visibility: <%= edit_visibility %>;">
|
||||
<i class="icon-remove"></i>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<% if (allowed_to_update) { %>
|
||||
<div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
|
||||
<i class="icon-remove" ></i>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
</li>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
|
||||
|
||||
<%
|
||||
|
|
@ -158,8 +184,56 @@ if (show_disabled) {
|
|||
</script>
|
||||
|
||||
|
||||
<script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate">
|
||||
|
||||
<div>
|
||||
<% if (is_todo) { %>
|
||||
<% if (inline) { %>
|
||||
<strong>Inline</strong> TODO on line: <%= line_no %>
|
||||
<% if (version_info) { %>
|
||||
<%= version_info %>
|
||||
<% } %>
|
||||
<br/>
|
||||
File: <code><%- file_name -%></code>
|
||||
<% } else { %>
|
||||
<% if (review_status) { %>
|
||||
<i class="icon-circle review-status-<%= review_status %>"></i>
|
||||
<% } %>
|
||||
<strong>General</strong> TODO
|
||||
<% if (version_info) { %>
|
||||
<%= version_info %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<% if (inline) { %>
|
||||
<strong>Inline</strong> comment on line: <%= line_no %>
|
||||
<% if (version_info) { %>
|
||||
<%= version_info %>
|
||||
<% } %>
|
||||
<br/>
|
||||
File: <code><%- file_name -%></code>
|
||||
<% } else { %>
|
||||
<% if (review_status) { %>
|
||||
<i class="icon-circle review-status-<%= review_status %>"></i>
|
||||
<% } %>
|
||||
<strong>General</strong> comment
|
||||
<% if (version_info) { %>
|
||||
<%= version_info %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<br/>
|
||||
Created:
|
||||
<time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time>
|
||||
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
##// END OF EJS Templates
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// registers the templates into global cache
|
||||
registerTemplates();
|
||||
|
|
|
|||
|
|
@ -360,13 +360,13 @@ ${self.plaintext_footer()}
|
|||
|
||||
div.markdown-block ul.checkbox li, div.markdown-block ol.checkbox li {
|
||||
list-style: none !important;
|
||||
margin: 6px !important;
|
||||
margin: 0px !important;
|
||||
padding: 0 !important
|
||||
}
|
||||
|
||||
div.markdown-block ul li, div.markdown-block ol li {
|
||||
list-style: disc !important;
|
||||
margin: 6px !important;
|
||||
margin: 0px !important;
|
||||
padding: 0 !important
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,21 +19,74 @@
|
|||
<div class="box">
|
||||
${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
|
||||
|
||||
<div class="box pr-summary">
|
||||
<div class="box">
|
||||
|
||||
<div class="summary-details block-left">
|
||||
|
||||
|
||||
<div class="pr-details-title">
|
||||
${_('New pull request')}
|
||||
</div>
|
||||
|
||||
<div class="form" style="padding-top: 10px">
|
||||
<!-- fields -->
|
||||
|
||||
<div class="fields" >
|
||||
|
||||
<div class="field">
|
||||
## COMMIT FLOW
|
||||
<div class="field">
|
||||
<div class="label label-textarea">
|
||||
<label for="commit_flow">${_('Commit flow')}:</label>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="flex-container">
|
||||
<div style="width: 45%;">
|
||||
<div class="panel panel-default source-panel">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">${_('Source repository')}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="display:none">${c.rhodecode_db_repo.description}</div>
|
||||
${h.hidden('source_repo')}
|
||||
${h.hidden('source_ref')}
|
||||
|
||||
<div id="pr_open_message"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="width: 90px; text-align: center; padding-top: 30px">
|
||||
<div>
|
||||
<i class="icon-right" style="font-size: 2.2em"></i>
|
||||
</div>
|
||||
<div style="position: relative; top: 10px">
|
||||
<span class="tag tag">
|
||||
<span id="switch_base"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="width: 45%;">
|
||||
|
||||
<div class="panel panel-default target-panel">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">${_('Target repository')}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="display:none" id="target_repo_desc"></div>
|
||||
${h.hidden('target_repo')}
|
||||
${h.hidden('target_ref')}
|
||||
<span id="target_ref_loading" style="display: none">
|
||||
${_('Loading refs...')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
## TITLE
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
<label for="pullrequest_title">${_('Title')}:</label>
|
||||
</div>
|
||||
|
|
@ -43,8 +96,9 @@
|
|||
<p class="help-block">
|
||||
Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## DESC
|
||||
<div class="field">
|
||||
<div class="label label-textarea">
|
||||
<label for="pullrequest_desc">${_('Description')}:</label>
|
||||
|
|
@ -55,39 +109,49 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
## REVIEWERS
|
||||
<div class="field">
|
||||
<div class="label label-textarea">
|
||||
<label for="commit_flow">${_('Commit flow')}:</label>
|
||||
</div>
|
||||
|
||||
## TODO: johbo: Abusing the "content" class here to get the
|
||||
## desired effect. Should be replaced by a proper solution.
|
||||
|
||||
##ORG
|
||||
<div class="content">
|
||||
<strong>${_('Source repository')}:</strong>
|
||||
${c.rhodecode_db_repo.description}
|
||||
<label for="pullrequest_reviewers">${_('Reviewers')}:</label>
|
||||
</div>
|
||||
<div class="content">
|
||||
${h.hidden('source_repo')}
|
||||
${h.hidden('source_ref')}
|
||||
</div>
|
||||
## REVIEW RULES
|
||||
<div id="review_rules" style="display: none" class="reviewers-title">
|
||||
<div class="pr-details-title">
|
||||
${_('Reviewer rules')}
|
||||
</div>
|
||||
<div class="pr-reviewer-rules">
|
||||
## review rules will be appended here, by default reviewers logic
|
||||
</div>
|
||||
</div>
|
||||
|
||||
##OTHER, most Probably the PARENT OF THIS FORK
|
||||
<div class="content">
|
||||
## filled with JS
|
||||
<div id="target_repo_desc"></div>
|
||||
</div>
|
||||
## REVIEWERS
|
||||
<div class="reviewers-title">
|
||||
<div class="pr-details-title">
|
||||
${_('Pull request reviewers')}
|
||||
<span class="calculate-reviewers"> - ${_('loading...')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reviewers" class="pr-details-content reviewers">
|
||||
## members goes here, filled via JS based on initial selection !
|
||||
<input type="hidden" name="__start__" value="review_members:sequence">
|
||||
<table id="review_members" class="group_members">
|
||||
## This content is loaded via JS and ReviewersPanel
|
||||
</table>
|
||||
<input type="hidden" name="__end__" value="review_members:sequence">
|
||||
|
||||
<div class="content">
|
||||
${h.hidden('target_repo')}
|
||||
${h.hidden('target_ref')}
|
||||
<span id="target_ref_loading" style="display: none">
|
||||
${_('Loading refs...')}
|
||||
</span>
|
||||
<div id="add_reviewer_input" class='ac'>
|
||||
<div class="reviewer_ac">
|
||||
${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
|
||||
<div id="reviewers_container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## SUBMIT
|
||||
<div class="field">
|
||||
<div class="label label-textarea">
|
||||
<label for="pullrequest_submit"></label>
|
||||
|
|
@ -96,66 +160,14 @@
|
|||
<div class="pr-submit-button">
|
||||
<input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
|
||||
</div>
|
||||
<div id="pr_open_message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pr-spacing-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
## AUTHOR
|
||||
<div class="reviewers-title block-right">
|
||||
<div class="pr-details-title">
|
||||
${_('Author of this pull request')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-right pr-details-content reviewers">
|
||||
<ul class="group_members">
|
||||
<li>
|
||||
${self.gravatar_with_user(c.rhodecode_user.email, 16, tooltip=True)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
## REVIEW RULES
|
||||
<div id="review_rules" style="display: none" class="reviewers-title block-right">
|
||||
<div class="pr-details-title">
|
||||
${_('Reviewer rules')}
|
||||
</div>
|
||||
<div class="pr-reviewer-rules">
|
||||
## review rules will be appended here, by default reviewers logic
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## REVIEWERS
|
||||
<div class="reviewers-title block-right">
|
||||
<div class="pr-details-title">
|
||||
${_('Pull request reviewers')}
|
||||
<span class="calculate-reviewers"> - ${_('loading...')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reviewers" class="block-right pr-details-content reviewers">
|
||||
## members goes here, filled via JS based on initial selection !
|
||||
<input type="hidden" name="__start__" value="review_members:sequence">
|
||||
<ul id="review_members" class="group_members"></ul>
|
||||
<input type="hidden" name="__end__" value="review_members:sequence">
|
||||
<div id="add_reviewer_input" class='ac'>
|
||||
<div class="reviewer_ac">
|
||||
${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
|
||||
<div id="reviewers_container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div>
|
||||
## overview pulled by ajax
|
||||
<div id="pull_request_overview"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${h.end_form()}
|
||||
</div>
|
||||
|
||||
|
|
@ -243,8 +255,6 @@
|
|||
|
||||
var diffDataHandler = function(data) {
|
||||
|
||||
$('#pull_request_overview').html(data);
|
||||
|
||||
var commitElements = data['commits'];
|
||||
var files = data['files'];
|
||||
var added = data['stats'][0]
|
||||
|
|
@ -303,27 +313,33 @@
|
|||
|
||||
msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
|
||||
msg += _ngettext(
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
'This pull requests will consist of <strong>{0} commits</strong>.',
|
||||
'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>',
|
||||
commitElements.length).format(commitElements.length)
|
||||
|
||||
msg += '\n';
|
||||
msg += '';
|
||||
msg += _ngettext(
|
||||
'<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ',
|
||||
'<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.',
|
||||
files.length).format(files.length)
|
||||
msg += '<span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted</span>.'.format(added, deleted)
|
||||
|
||||
msg += '\n\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
|
||||
msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted)
|
||||
|
||||
msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
|
||||
|
||||
if (commitElements.length) {
|
||||
var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
|
||||
prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
|
||||
}
|
||||
else {
|
||||
prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
|
||||
var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
|
||||
_gettext('There are no commits to merge.'));
|
||||
prButtonLock(true, noCommitsMsg, 'compare');
|
||||
}
|
||||
|
||||
//make both panels equal
|
||||
$('.target-panel').height($('.source-panel').height())
|
||||
|
||||
};
|
||||
|
||||
reviewersController = new ReviewersController();
|
||||
|
|
@ -429,10 +445,12 @@
|
|||
|
||||
var targetRepoChanged = function(repoData) {
|
||||
// generate new DESC of target repo displayed next to select
|
||||
|
||||
$('#target_repo_desc').html(repoData['description']);
|
||||
|
||||
var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
|
||||
$('#target_repo_desc').html(
|
||||
"<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
|
||||
);
|
||||
var title = _gettext('Switch target repository with the source.')
|
||||
$('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
|
||||
|
||||
// generate dynamic select2 for refs.
|
||||
initTargetRefs(repoData['refs']['select2_refs'],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<%inherit file="/base/base.mako"/>
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
|
||||
<%namespace name="sidebar" file="/base/sidebar.mako"/>
|
||||
|
||||
|
||||
<%def name="title()">
|
||||
${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
|
||||
|
|
@ -21,12 +23,19 @@
|
|||
${self.repo_menu(active='showpullrequest')}
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="main()">
|
||||
## Container to gather extracted Tickets
|
||||
<%
|
||||
c.referenced_commit_issues = []
|
||||
c.referenced_desc_issues = []
|
||||
%>
|
||||
|
||||
<script type="text/javascript">
|
||||
// TODO: marcink switch this to pyroutes
|
||||
AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
|
||||
templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
|
||||
templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
|
||||
</script>
|
||||
|
||||
<div class="box">
|
||||
|
|
@ -79,7 +88,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)}
|
||||
${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
|
||||
</div>
|
||||
|
||||
<div id="pr-desc-edit" class="input textarea" style="display: none;">
|
||||
|
|
@ -89,29 +98,6 @@
|
|||
|
||||
<div id="summary" class="fields pr-details-content">
|
||||
|
||||
## review
|
||||
<div class="field">
|
||||
<div class="label-pr-detail">
|
||||
<label>${_('Review status')}:</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
%if c.pull_request_review_status:
|
||||
<div class="tag status-tag-${c.pull_request_review_status}">
|
||||
<i class="icon-circle review-status-${c.pull_request_review_status}"></i>
|
||||
<span class="changeset-status-lbl">
|
||||
%if c.pull_request.is_closed():
|
||||
${_('Closed')},
|
||||
%endif
|
||||
|
||||
${h.commit_status_lbl(c.pull_request_review_status)}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
- ${_ungettext('calculated based on {} reviewer vote', 'calculated based on {} reviewers votes', len(c.pull_request_reviewers)).format(len(c.pull_request_reviewers))}
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## source
|
||||
<div class="field">
|
||||
<div class="label-pr-detail">
|
||||
|
|
@ -136,7 +122,7 @@
|
|||
|
||||
${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
|
||||
|
||||
<a class="source-details-action" href="#expand-source-details" onclick="return versionController.toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
|
||||
<a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
|
||||
<i class="icon-angle-down">more details</i>
|
||||
</a>
|
||||
|
||||
|
|
@ -231,7 +217,7 @@
|
|||
</code>
|
||||
</td>
|
||||
<td>
|
||||
<input ${('checked="checked"' if c.from_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
|
||||
<input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
|
||||
<input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
|
||||
</td>
|
||||
<td>
|
||||
|
|
@ -280,159 +266,12 @@
|
|||
|
||||
</div>
|
||||
|
||||
## REVIEW RULES
|
||||
<div id="review_rules" style="display: none" class="reviewers-title block-right">
|
||||
<div class="pr-details-title">
|
||||
${_('Reviewer rules')}
|
||||
%if c.allowed_to_update:
|
||||
<span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
|
||||
%endif
|
||||
</div>
|
||||
<div class="pr-reviewer-rules">
|
||||
## review rules will be appended here, by default reviewers logic
|
||||
</div>
|
||||
<input id="review_data" type="hidden" name="review_data" value="">
|
||||
</div>
|
||||
|
||||
## REVIEWERS
|
||||
<div class="reviewers-title first-panel block-right">
|
||||
<div class="pr-details-title">
|
||||
${_('Pull request reviewers')}
|
||||
%if c.allowed_to_update:
|
||||
<span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
<div id="reviewers" class="block-right pr-details-content reviewers">
|
||||
|
||||
## members redering block
|
||||
<input type="hidden" name="__start__" value="review_members:sequence">
|
||||
<ul id="review_members" class="group_members">
|
||||
|
||||
% for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
|
||||
<script>
|
||||
var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
|
||||
var status = "${(status[0][1].status if status else 'not_reviewed')}";
|
||||
var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
|
||||
var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
|
||||
|
||||
var entry = renderTemplate('reviewMemberEntry', {
|
||||
'member': member,
|
||||
'mandatory': member.mandatory,
|
||||
'reasons': member.reasons,
|
||||
'allowed_to_update': allowed_to_update,
|
||||
'review_status': status,
|
||||
'review_status_label': status_lbl,
|
||||
'user_group': member.user_group,
|
||||
'create': false
|
||||
});
|
||||
$('#review_members').append(entry)
|
||||
</script>
|
||||
|
||||
% endfor
|
||||
|
||||
</ul>
|
||||
|
||||
<input type="hidden" name="__end__" value="review_members:sequence">
|
||||
## end members redering block
|
||||
|
||||
%if not c.pull_request.is_closed():
|
||||
<div id="add_reviewer" class="ac" style="display: none;">
|
||||
%if c.allowed_to_update:
|
||||
% if not c.forbid_adding_reviewers:
|
||||
<div id="add_reviewer_input" class="reviewer_ac">
|
||||
${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
|
||||
<div id="reviewers_container"></div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="pull-right">
|
||||
<button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
|
||||
## TODOs will be listed here
|
||||
<div class="reviewers-title block-right">
|
||||
<div class="pr-details-title">
|
||||
## Only show unresolved, that is only what matters
|
||||
TODO Comments - ${len(c.unresolved_comments)} / ${(len(c.unresolved_comments) + len(c.resolved_comments))}
|
||||
|
||||
% if not c.at_version:
|
||||
% if c.resolved_comments:
|
||||
<span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return versionController.toggleElement(this, '.unresolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
|
||||
% else:
|
||||
<span class="block-right last-item noselect">Show resolved</span>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-right pr-details-content reviewers">
|
||||
|
||||
<table class="todo-table">
|
||||
<%
|
||||
def sorter(entry):
|
||||
user_id = entry.author.user_id
|
||||
resolved = '1' if entry.resolved else '0'
|
||||
if user_id == c.rhodecode_user.user_id:
|
||||
# own comments first
|
||||
user_id = 0
|
||||
return '{}_{}_{}'.format(resolved, user_id, str(entry.comment_id).zfill(100))
|
||||
%>
|
||||
|
||||
% if c.at_version:
|
||||
<tr>
|
||||
<td class="unresolved-todo-text">${_('unresolved TODOs unavailable in this view')}.</td>
|
||||
</tr>
|
||||
% else:
|
||||
% for todo_comment in sorted(c.unresolved_comments + c.resolved_comments, key=sorter):
|
||||
<% resolved = todo_comment.resolved %>
|
||||
% if inline:
|
||||
<% outdated_at_ver = todo_comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
|
||||
% else:
|
||||
<% outdated_at_ver = todo_comment.older_than_version(getattr(c, 'at_version_num', None)) %>
|
||||
% endif
|
||||
|
||||
<tr ${('class="unresolved-todo" style="display: none"' if resolved else '') |n}>
|
||||
|
||||
<td class="td-todo-number">
|
||||
% if resolved:
|
||||
<a class="permalink todo-resolved tooltip" title="${_('Resolved by comment #{}').format(todo_comment.resolved.comment_id)}" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
|
||||
<i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
|
||||
% else:
|
||||
<a class="permalink" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
|
||||
<i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
|
||||
% endif
|
||||
</td>
|
||||
<td class="td-todo-gravatar">
|
||||
${base.gravatar(todo_comment.author.email, 16, user=todo_comment.author, tooltip=True, extra_class=['no-margin'])}
|
||||
</td>
|
||||
<td class="todo-comment-text-wrapper">
|
||||
<div class="todo-comment-text">
|
||||
<code>${h.chop_at_smart(todo_comment.text, '\n', suffix_if_chopped='...')}</code>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
% endfor
|
||||
|
||||
% if len(c.unresolved_comments) == 0:
|
||||
<tr>
|
||||
<td class="unresolved-todo-text">${_('No unresolved TODOs')}.</td>
|
||||
</tr>
|
||||
% endif
|
||||
|
||||
% endif
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
|
||||
% if c.state_progressing:
|
||||
|
||||
|
|
@ -484,9 +323,9 @@
|
|||
<div class="compare_view_commits_title">
|
||||
% if not c.compare_mode:
|
||||
|
||||
% if c.at_version_pos:
|
||||
% if c.at_version_index:
|
||||
<h4>
|
||||
${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
|
||||
${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
|
||||
</h4>
|
||||
% endif
|
||||
|
||||
|
|
@ -539,10 +378,11 @@
|
|||
</div>
|
||||
|
||||
% if not c.missing_commits:
|
||||
## COMPARE RANGE DIFF MODE
|
||||
% if c.compare_mode:
|
||||
% if c.at_version:
|
||||
<h4>
|
||||
${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
|
||||
${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
|
||||
</h4>
|
||||
|
||||
<div class="subtitle-compare">
|
||||
|
|
@ -597,7 +437,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)}</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=c.referenced_commit_issues)}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -608,19 +448,13 @@
|
|||
|
||||
% endif
|
||||
|
||||
## Regular DIFF
|
||||
% else:
|
||||
<%include file="/compare/compare_commits.mako" />
|
||||
% endif
|
||||
|
||||
<div class="cs_files">
|
||||
<%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
|
||||
% if c.at_version:
|
||||
<% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
|
||||
<% c.comments = c.comment_versions[c.at_version_num]['display'] %>
|
||||
% else:
|
||||
<% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
|
||||
<% c.comments = c.comment_versions[c.at_version_num]['until'] %>
|
||||
% endif
|
||||
|
||||
<%
|
||||
pr_menu_data = {
|
||||
|
|
@ -667,7 +501,7 @@
|
|||
## comments heading with count
|
||||
<div class="comments-heading">
|
||||
<i class="icon-comment"></i>
|
||||
${_('Comments')} ${len(c.comments)}
|
||||
${_('General Comments')} ${len(c.comments)}
|
||||
</div>
|
||||
|
||||
## render general comments
|
||||
|
|
@ -704,218 +538,456 @@
|
|||
% endif
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
versionController = new VersionController();
|
||||
versionController.init();
|
||||
### NAV SIDEBAR
|
||||
<aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
|
||||
<div class="sidenav navbar__inner" >
|
||||
## TOGGLE
|
||||
<div class="sidebar-toggle" onclick="toggleSidebar(); return false">
|
||||
<a href="#toggleSidebar" class="grey-link-action">
|
||||
|
||||
reviewersController = new ReviewersController();
|
||||
commitsController = new CommitsController();
|
||||
</a>
|
||||
</div>
|
||||
|
||||
updateController = new UpdatePrController();
|
||||
## CONTENT
|
||||
<div class="sidebar-content">
|
||||
|
||||
$(function () {
|
||||
## RULES SUMMARY/RULES
|
||||
<div class="sidebar-element clear-both">
|
||||
<% vote_title = _ungettext(
|
||||
'Status calculated based on votes from {} reviewer',
|
||||
'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))
|
||||
%>
|
||||
|
||||
// custom code mirror
|
||||
var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
|
||||
<i class="icon-circle review-status-${c.pull_request_review_status}"></i>
|
||||
${len(c.allowed_reviewers)}
|
||||
</div>
|
||||
|
||||
var PRDetails = {
|
||||
editButton: $('#open_edit_pullrequest'),
|
||||
closeButton: $('#close_edit_pullrequest'),
|
||||
deleteButton: $('#delete_pullrequest'),
|
||||
viewFields: $('#pr-desc, #pr-title'),
|
||||
editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'),
|
||||
## REVIEW RULES
|
||||
<div id="review_rules" style="display: none" class="">
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
<span class="sidebar-heading">
|
||||
${_('Reviewer rules')}
|
||||
</span>
|
||||
|
||||
init: function () {
|
||||
var that = this;
|
||||
this.editButton.on('click', function (e) {
|
||||
that.edit();
|
||||
});
|
||||
this.closeButton.on('click', function (e) {
|
||||
that.view();
|
||||
});
|
||||
},
|
||||
</div>
|
||||
<div class="pr-reviewer-rules">
|
||||
## review rules will be appended here, by default reviewers logic
|
||||
</div>
|
||||
<input id="review_data" type="hidden" name="review_data" value="">
|
||||
</div>
|
||||
|
||||
edit: function (event) {
|
||||
this.viewFields.hide();
|
||||
this.editButton.hide();
|
||||
this.deleteButton.hide();
|
||||
this.closeButton.show();
|
||||
this.editFields.show();
|
||||
codeMirrorInstance.refresh();
|
||||
},
|
||||
## REVIEWERS
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
<span class="tooltip sidebar-heading" title="${vote_title}">
|
||||
<i class="icon-circle review-status-${c.pull_request_review_status}"></i>
|
||||
${_('Reviewers')}
|
||||
</span>
|
||||
%if c.allowed_to_update:
|
||||
<span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
|
||||
<span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
|
||||
%else:
|
||||
<span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
|
||||
<span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
|
||||
%endif
|
||||
</div>
|
||||
|
||||
view: function (event) {
|
||||
this.editButton.show();
|
||||
this.deleteButton.show();
|
||||
this.editFields.hide();
|
||||
this.closeButton.hide();
|
||||
this.viewFields.show();
|
||||
}
|
||||
};
|
||||
<div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
|
||||
|
||||
var ReviewersPanel = {
|
||||
editButton: $('#open_edit_reviewers'),
|
||||
closeButton: $('#close_edit_reviewers'),
|
||||
addButton: $('#add_reviewer'),
|
||||
removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
|
||||
## members redering block
|
||||
<input type="hidden" name="__start__" value="review_members:sequence">
|
||||
|
||||
init: function () {
|
||||
var self = this;
|
||||
this.editButton.on('click', function (e) {
|
||||
self.edit();
|
||||
});
|
||||
this.closeButton.on('click', function (e) {
|
||||
self.close();
|
||||
});
|
||||
},
|
||||
<table id="review_members" class="group_members">
|
||||
## This content is loaded via JS and ReviewersPanel
|
||||
</table>
|
||||
|
||||
edit: function (event) {
|
||||
this.editButton.hide();
|
||||
this.closeButton.show();
|
||||
this.addButton.show();
|
||||
this.removeButtons.css('visibility', 'visible');
|
||||
// review rules
|
||||
reviewersController.loadReviewRules(
|
||||
${c.pull_request.reviewer_data_json | n});
|
||||
},
|
||||
<input type="hidden" name="__end__" value="review_members:sequence">
|
||||
## end members redering block
|
||||
|
||||
close: function (event) {
|
||||
this.editButton.show();
|
||||
this.closeButton.hide();
|
||||
this.addButton.hide();
|
||||
this.removeButtons.css('visibility', 'hidden');
|
||||
// hide review rules
|
||||
reviewersController.hideReviewRules()
|
||||
}
|
||||
};
|
||||
%if not c.pull_request.is_closed():
|
||||
<div id="add_reviewer" class="ac" style="display: none;">
|
||||
%if c.allowed_to_update:
|
||||
% if not c.forbid_adding_reviewers:
|
||||
<div id="add_reviewer_input" class="reviewer_ac">
|
||||
${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
|
||||
<div id="reviewers_container"></div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="pull-right">
|
||||
<button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
PRDetails.init();
|
||||
ReviewersPanel.init();
|
||||
## ## OBSERVERS
|
||||
## <div class="sidebar-element clear-both">
|
||||
## <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}">
|
||||
## <i class="icon-eye"></i>
|
||||
## 0
|
||||
## </div>
|
||||
##
|
||||
## <div class="right-sidebar-expanded-state pr-details-title">
|
||||
## <span class="sidebar-heading">
|
||||
## <i class="icon-eye"></i>
|
||||
## ${_('Observers')}
|
||||
## </span>
|
||||
## </div>
|
||||
## <div class="right-sidebar-expanded-state pr-details-content">
|
||||
## No observers
|
||||
## </div>
|
||||
## </div>
|
||||
|
||||
showOutdated = function (self) {
|
||||
$('.comment-inline.comment-outdated').show();
|
||||
$('.filediff-outdated').show();
|
||||
$('.showOutdatedComments').hide();
|
||||
$('.hideOutdatedComments').show();
|
||||
};
|
||||
## TODOs
|
||||
<div class="sidebar-element clear-both">
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
|
||||
<i class="icon-flag-filled"></i>
|
||||
<span id="todos-count">${len(c.unresolved_comments)}</span>
|
||||
</div>
|
||||
|
||||
hideOutdated = function (self) {
|
||||
$('.comment-inline.comment-outdated').hide();
|
||||
$('.filediff-outdated').hide();
|
||||
$('.hideOutdatedComments').hide();
|
||||
$('.showOutdatedComments').show();
|
||||
};
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
## Only show unresolved, that is only what matters
|
||||
<span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
|
||||
<i class="icon-flag-filled"></i>
|
||||
TODOs
|
||||
</span>
|
||||
|
||||
refreshMergeChecks = function () {
|
||||
var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
|
||||
$('.pull-request-merge').css('opacity', 0.3);
|
||||
$('.action-buttons-extra').css('opacity', 0.3);
|
||||
% if not c.at_version:
|
||||
% if c.resolved_comments:
|
||||
<span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
|
||||
% else:
|
||||
<span class="block-right last-item noselect">Show resolved</span>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
|
||||
$('.pull-request-merge').load(
|
||||
loadUrl, function () {
|
||||
$('.pull-request-merge').css('opacity', 1);
|
||||
<div class="right-sidebar-expanded-state pr-details-content">
|
||||
|
||||
$('.action-buttons-extra').css('opacity', 1);
|
||||
}
|
||||
);
|
||||
};
|
||||
% if c.at_version:
|
||||
<table>
|
||||
<tr>
|
||||
<td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
|
||||
</tr>
|
||||
</table>
|
||||
% else:
|
||||
% if c.unresolved_comments + c.resolved_comments:
|
||||
${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
|
||||
% else:
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
${_('No TODOs yet')}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
closePullRequest = function (status) {
|
||||
if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
|
||||
return false;
|
||||
}
|
||||
// inject closing flag
|
||||
$('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
|
||||
$(generalCommentForm.statusChange).select2("val", status).trigger('change');
|
||||
$(generalCommentForm.submitForm).submit();
|
||||
};
|
||||
## COMMENTS
|
||||
<div class="sidebar-element clear-both">
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
|
||||
<i class="icon-comment" style="color: #949494"></i>
|
||||
<span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
|
||||
<span class="display-none" id="general-comments-count">${len(c.comments)}</span>
|
||||
<span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
|
||||
</div>
|
||||
|
||||
$('#show-outdated-comments').on('click', function (e) {
|
||||
var button = $(this);
|
||||
var outdated = $('.comment-outdated');
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
<span class="sidebar-heading noselect" onclick="refreshComments(); return false">
|
||||
<i class="icon-comment" style="color: #949494"></i>
|
||||
${_('Comments')}
|
||||
|
||||
if (button.html() === "(Show)") {
|
||||
button.html("(Hide)");
|
||||
outdated.show();
|
||||
} else {
|
||||
button.html("(Show)");
|
||||
outdated.hide();
|
||||
}
|
||||
});
|
||||
## % if outdated_comm_count_ver:
|
||||
## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
|
||||
## (${_("{} Outdated").format(outdated_comm_count_ver)})
|
||||
## </a>
|
||||
## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
|
||||
## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
|
||||
|
||||
$('.show-inline-comments').on('change', function (e) {
|
||||
var show = 'none';
|
||||
var target = e.currentTarget;
|
||||
if (target.checked) {
|
||||
show = ''
|
||||
}
|
||||
var boxid = $(target).attr('id_for');
|
||||
var comments = $('#{0} .inline-comments'.format(boxid));
|
||||
var fn_display = function (idx) {
|
||||
$(this).css('display', show);
|
||||
};
|
||||
$(comments).each(fn_display);
|
||||
var btns = $('#{0} .inline-comments-button'.format(boxid));
|
||||
$(btns).each(fn_display);
|
||||
});
|
||||
## % else:
|
||||
## (${_("{} Outdated").format(outdated_comm_count_ver)})
|
||||
## % endif
|
||||
|
||||
$('#merge_pull_request_form').submit(function () {
|
||||
if (!$('#merge_pull_request').attr('disabled')) {
|
||||
$('#merge_pull_request').attr('disabled', 'disabled');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
</span>
|
||||
|
||||
$('#edit_pull_request').on('click', function (e) {
|
||||
var title = $('#pr-title-input').val();
|
||||
var description = codeMirrorInstance.getValue();
|
||||
var renderer = $('#pr-renderer-input').val();
|
||||
editPullRequest(
|
||||
"${c.repo_name}", "${c.pull_request.pull_request_id}",
|
||||
title, description, renderer);
|
||||
});
|
||||
% if outdated_comm_count_ver:
|
||||
<span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
|
||||
% else:
|
||||
<span class="block-right last-item noselect">Show hidden</span>
|
||||
% endif
|
||||
|
||||
$('#update_pull_request').on('click', function (e) {
|
||||
$(this).attr('disabled', 'disabled');
|
||||
$(this).addClass('disabled');
|
||||
$(this).html(_gettext('Saving...'));
|
||||
reviewersController.updateReviewers(
|
||||
"${c.repo_name}", "${c.pull_request.pull_request_id}");
|
||||
});
|
||||
</div>
|
||||
|
||||
<div class="right-sidebar-expanded-state pr-details-content">
|
||||
% if c.inline_comments_flat + c.comments:
|
||||
${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
|
||||
% else:
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
${_('No Comments yet')}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
// fixing issue with caches on firefox
|
||||
$('#update_commits').removeAttr("disabled");
|
||||
</div>
|
||||
|
||||
$('.show-inline-comments').on('click', function (e) {
|
||||
var boxid = $(this).attr('data-comment-id');
|
||||
var button = $(this);
|
||||
## Referenced Tickets
|
||||
<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))}
|
||||
</div>
|
||||
|
||||
if (button.hasClass("comments-visible")) {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function (index) {
|
||||
$(this).hide();
|
||||
});
|
||||
button.removeClass("comments-visible");
|
||||
} else {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function (index) {
|
||||
$(this).show();
|
||||
});
|
||||
button.addClass("comments-visible");
|
||||
}
|
||||
});
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
<span class="sidebar-heading">
|
||||
<i class="icon-info-circled"></i>
|
||||
${_('Referenced Tickets')}
|
||||
</span>
|
||||
</div>
|
||||
<div class="right-sidebar-expanded-state pr-details-content">
|
||||
<table>
|
||||
|
||||
// register submit callback on commentForm form to track TODOs
|
||||
window.commentFormGlobalSubmitSuccessCallback = function () {
|
||||
refreshMergeChecks();
|
||||
};
|
||||
<tr><td><code>${_('In pull request description')}:</code></td></tr>
|
||||
% if c.referenced_desc_issues:
|
||||
% for ticket_dict in c.referenced_desc_issues:
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${ticket_dict.get('url')}">
|
||||
${ticket_dict.get('id')}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
% else:
|
||||
<tr>
|
||||
<td>
|
||||
${_('No Ticket data found.')}
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
|
||||
ReviewerAutoComplete('#user');
|
||||
<tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
|
||||
% if c.referenced_commit_issues:
|
||||
% for ticket_dict in c.referenced_commit_issues:
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${ticket_dict.get('url')}">
|
||||
${ticket_dict.get('id')}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
% else:
|
||||
<tr>
|
||||
<td>
|
||||
${_('No Ticket data found.')}
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
</table>
|
||||
|
||||
})
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
## This JS needs to be at the end
|
||||
<script type="text/javascript">
|
||||
|
||||
versionController = new VersionController();
|
||||
versionController.init();
|
||||
|
||||
reviewersController = new ReviewersController();
|
||||
commitsController = new CommitsController();
|
||||
|
||||
updateController = new UpdatePrController();
|
||||
|
||||
window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
|
||||
window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// custom code mirror
|
||||
var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
|
||||
|
||||
var PRDetails = {
|
||||
editButton: $('#open_edit_pullrequest'),
|
||||
closeButton: $('#close_edit_pullrequest'),
|
||||
deleteButton: $('#delete_pullrequest'),
|
||||
viewFields: $('#pr-desc, #pr-title'),
|
||||
editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'),
|
||||
|
||||
init: function () {
|
||||
var that = this;
|
||||
this.editButton.on('click', function (e) {
|
||||
that.edit();
|
||||
});
|
||||
this.closeButton.on('click', function (e) {
|
||||
that.view();
|
||||
});
|
||||
},
|
||||
|
||||
edit: function (event) {
|
||||
var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
|
||||
this.viewFields.hide();
|
||||
this.editButton.hide();
|
||||
this.deleteButton.hide();
|
||||
this.closeButton.show();
|
||||
this.editFields.show();
|
||||
cmInstance.refresh();
|
||||
},
|
||||
|
||||
view: function (event) {
|
||||
this.editButton.show();
|
||||
this.deleteButton.show();
|
||||
this.editFields.hide();
|
||||
this.closeButton.hide();
|
||||
this.viewFields.show();
|
||||
}
|
||||
};
|
||||
|
||||
PRDetails.init();
|
||||
ReviewersPanel.init(reviewerRulesData, setReviewersData);
|
||||
|
||||
window.showOutdated = function (self) {
|
||||
$('.comment-inline.comment-outdated').show();
|
||||
$('.filediff-outdated').show();
|
||||
$('.showOutdatedComments').hide();
|
||||
$('.hideOutdatedComments').show();
|
||||
};
|
||||
|
||||
window.hideOutdated = function (self) {
|
||||
$('.comment-inline.comment-outdated').hide();
|
||||
$('.filediff-outdated').hide();
|
||||
$('.hideOutdatedComments').hide();
|
||||
$('.showOutdatedComments').show();
|
||||
};
|
||||
|
||||
window.refreshMergeChecks = function () {
|
||||
var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
|
||||
$('.pull-request-merge').css('opacity', 0.3);
|
||||
$('.action-buttons-extra').css('opacity', 0.3);
|
||||
|
||||
$('.pull-request-merge').load(
|
||||
loadUrl, function () {
|
||||
$('.pull-request-merge').css('opacity', 1);
|
||||
|
||||
$('.action-buttons-extra').css('opacity', 1);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
window.closePullRequest = function (status) {
|
||||
if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
|
||||
return false;
|
||||
}
|
||||
// inject closing flag
|
||||
$('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
|
||||
$(generalCommentForm.statusChange).select2("val", status).trigger('change');
|
||||
$(generalCommentForm.submitForm).submit();
|
||||
};
|
||||
|
||||
//TODO this functionality is now missing
|
||||
$('#show-outdated-comments').on('click', function (e) {
|
||||
var button = $(this);
|
||||
var outdated = $('.comment-outdated');
|
||||
|
||||
if (button.html() === "(Show)") {
|
||||
button.html("(Hide)");
|
||||
outdated.show();
|
||||
} else {
|
||||
button.html("(Show)");
|
||||
outdated.hide();
|
||||
}
|
||||
});
|
||||
|
||||
$('#merge_pull_request_form').submit(function () {
|
||||
if (!$('#merge_pull_request').attr('disabled')) {
|
||||
$('#merge_pull_request').attr('disabled', 'disabled');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
$('#edit_pull_request').on('click', function (e) {
|
||||
var title = $('#pr-title-input').val();
|
||||
var description = codeMirrorInstance.getValue();
|
||||
var renderer = $('#pr-renderer-input').val();
|
||||
editPullRequest(
|
||||
"${c.repo_name}", "${c.pull_request.pull_request_id}",
|
||||
title, description, renderer);
|
||||
});
|
||||
|
||||
$('#update_pull_request').on('click', function (e) {
|
||||
$(this).attr('disabled', 'disabled');
|
||||
$(this).addClass('disabled');
|
||||
$(this).html(_gettext('Saving...'));
|
||||
reviewersController.updateReviewers(
|
||||
"${c.repo_name}", "${c.pull_request.pull_request_id}");
|
||||
});
|
||||
|
||||
// fixing issue with caches on firefox
|
||||
$('#update_commits').removeAttr("disabled");
|
||||
|
||||
$('.show-inline-comments').on('click', function (e) {
|
||||
var boxid = $(this).attr('data-comment-id');
|
||||
var button = $(this);
|
||||
|
||||
if (button.hasClass("comments-visible")) {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function (index) {
|
||||
$(this).hide();
|
||||
});
|
||||
button.removeClass("comments-visible");
|
||||
} else {
|
||||
$('#{0} .inline-comments'.format(boxid)).each(function (index) {
|
||||
$(this).show();
|
||||
});
|
||||
button.addClass("comments-visible");
|
||||
}
|
||||
});
|
||||
|
||||
$('.show-inline-comments').on('change', function (e) {
|
||||
var show = 'none';
|
||||
var target = e.currentTarget;
|
||||
if (target.checked) {
|
||||
show = ''
|
||||
}
|
||||
var boxid = $(target).attr('id_for');
|
||||
var comments = $('#{0} .inline-comments'.format(boxid));
|
||||
var fn_display = function (idx) {
|
||||
$(this).css('display', show);
|
||||
};
|
||||
$(comments).each(fn_display);
|
||||
var btns = $('#{0} .inline-comments-button'.format(boxid));
|
||||
$(btns).each(fn_display);
|
||||
});
|
||||
|
||||
// register submit callback on commentForm form to track TODOs
|
||||
window.commentFormGlobalSubmitSuccessCallback = function () {
|
||||
refreshMergeChecks();
|
||||
};
|
||||
|
||||
ReviewerAutoComplete('#user');
|
||||
|
||||
})();
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
var channel = '${c.pr_broadcast_channel}';
|
||||
new ReviewerPresenceController(channel)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
</%def>
|
||||
|
|
|
|||
|
|
@ -948,8 +948,8 @@ def assert_inline_comments(pull_request, visible=None, outdated=None):
|
|||
if visible is not None:
|
||||
inline_comments = CommentsModel().get_inline_comments(
|
||||
pull_request.target_repo.repo_id, pull_request=pull_request)
|
||||
inline_cnt = CommentsModel().get_inline_comments_count(
|
||||
inline_comments)
|
||||
inline_cnt = len(CommentsModel().get_inline_comments_as_list(
|
||||
inline_comments))
|
||||
assert inline_cnt == visible
|
||||
if outdated is not None:
|
||||
outdated_comments = CommentsModel().get_outdated_comments(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue