Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,46 | |||||
|
1 | |RCE| 4.24.1 |RNS| | |||
|
2 | ------------------ | |||
|
3 | ||||
|
4 | Release Date | |||
|
5 | ^^^^^^^^^^^^ | |||
|
6 | ||||
|
7 | - 2021-02-04 | |||
|
8 | ||||
|
9 | ||||
|
10 | New Features | |||
|
11 | ^^^^^^^^^^^^ | |||
|
12 | ||||
|
13 | ||||
|
14 | ||||
|
15 | General | |||
|
16 | ^^^^^^^ | |||
|
17 | ||||
|
18 | - Core: added statsd client for statistics usage. | |||
|
19 | - Clone urls: allow custom clone by id template so users can set clone-by-id as default. | |||
|
20 | - Automation: enable check for new version for EE edition as automation task that will send notifications when new RhodeCode version is available | |||
|
21 | ||||
|
22 | Security | |||
|
23 | ^^^^^^^^ | |||
|
24 | ||||
|
25 | ||||
|
26 | ||||
|
27 | Performance | |||
|
28 | ^^^^^^^^^^^ | |||
|
29 | ||||
|
30 | - Core: bumped git to 2.30.0 | |||
|
31 | ||||
|
32 | ||||
|
33 | Fixes | |||
|
34 | ^^^^^ | |||
|
35 | ||||
|
36 | - Comments: add ability to resolve todos from the side-bar. This should prevent situations | |||
|
37 | when a TODO was left over in outdated/removed code pieces, and users needs to search to resolve them. | |||
|
38 | - Pull requests: fixed a case when template marker was used in description field causing 500 errors on commenting. | |||
|
39 | - Merges: fixed excessive data saved in merge metadata that could not fit inside the DB table. | |||
|
40 | - Exceptions: fixed problem with exceptions formatting resulting in limited exception data reporting. | |||
|
41 | ||||
|
42 | ||||
|
43 | Upgrade notes | |||
|
44 | ^^^^^^^^^^^^^ | |||
|
45 | ||||
|
46 | - Un-scheduled release addressing problems in 4.24.X releases. |
@@ -0,0 +1,12 | |||||
|
1 | diff -rup pytest-4.6.5-orig/setup.py pytest-4.6.5/setup.py | |||
|
2 | --- pytest-4.6.5-orig/setup.py 2018-04-10 10:23:04.000000000 +0200 | |||
|
3 | +++ pytest-4.6.5/setup.py 2018-04-10 10:23:34.000000000 +0200 | |||
|
4 | @@ -24,7 +24,7 @@ INSTALL_REQUIRES = [ | |||
|
5 | def main(): | |||
|
6 | setup( | |||
|
7 | use_scm_version={"write_to": "src/_pytest/_version.py"}, | |||
|
8 | - setup_requires=["setuptools-scm", "setuptools>=40.0"], | |||
|
9 | + setup_requires=["setuptools-scm", "setuptools<=42.0"], | |||
|
10 | package_dir={"": "src"}, | |||
|
11 | # fmt: off | |||
|
12 | extras_require={ No newline at end of file |
@@ -0,0 +1,46 | |||||
|
1 | from __future__ import absolute_import, division, unicode_literals | |||
|
2 | ||||
|
3 | import logging | |||
|
4 | ||||
|
5 | from .stream import TCPStatsClient, UnixSocketStatsClient # noqa | |||
|
6 | from .udp import StatsClient # noqa | |||
|
7 | ||||
|
8 | HOST = 'localhost' | |||
|
9 | PORT = 8125 | |||
|
10 | IPV6 = False | |||
|
11 | PREFIX = None | |||
|
12 | MAXUDPSIZE = 512 | |||
|
13 | ||||
|
14 | log = logging.getLogger('rhodecode.statsd') | |||
|
15 | ||||
|
16 | ||||
|
17 | def statsd_config(config, prefix='statsd.'): | |||
|
18 | _config = {} | |||
|
19 | for key in config.keys(): | |||
|
20 | if key.startswith(prefix): | |||
|
21 | _config[key[len(prefix):]] = config[key] | |||
|
22 | return _config | |||
|
23 | ||||
|
24 | ||||
|
25 | def client_from_config(configuration, prefix='statsd.', **kwargs): | |||
|
26 | from pyramid.settings import asbool | |||
|
27 | ||||
|
28 | _config = statsd_config(configuration, prefix) | |||
|
29 | statsd_enabled = asbool(_config.pop('enabled', False)) | |||
|
30 | if not statsd_enabled: | |||
|
31 | log.debug('statsd client not enabled by statsd.enabled = flag, skipping...') | |||
|
32 | return | |||
|
33 | ||||
|
34 | host = _config.pop('statsd_host', HOST) | |||
|
35 | port = _config.pop('statsd_port', PORT) | |||
|
36 | prefix = _config.pop('statsd_prefix', PREFIX) | |||
|
37 | maxudpsize = _config.pop('statsd_maxudpsize', MAXUDPSIZE) | |||
|
38 | ipv6 = asbool(_config.pop('statsd_ipv6', IPV6)) | |||
|
39 | log.debug('configured statsd client %s:%s', host, port) | |||
|
40 | ||||
|
41 | return StatsClient( | |||
|
42 | host=host, port=port, prefix=prefix, maxudpsize=maxudpsize, ipv6=ipv6) | |||
|
43 | ||||
|
44 | ||||
|
45 | def get_statsd_client(request): | |||
|
46 | return client_from_config(request.registry.settings) |
@@ -0,0 +1,107 | |||||
|
1 | from __future__ import absolute_import, division, unicode_literals | |||
|
2 | ||||
|
3 | import random | |||
|
4 | from collections import deque | |||
|
5 | from datetime import timedelta | |||
|
6 | ||||
|
7 | from .timer import Timer | |||
|
8 | ||||
|
9 | ||||
|
10 | class StatsClientBase(object): | |||
|
11 | """A Base class for various statsd clients.""" | |||
|
12 | ||||
|
13 | def close(self): | |||
|
14 | """Used to close and clean up any underlying resources.""" | |||
|
15 | raise NotImplementedError() | |||
|
16 | ||||
|
17 | def _send(self): | |||
|
18 | raise NotImplementedError() | |||
|
19 | ||||
|
20 | def pipeline(self): | |||
|
21 | raise NotImplementedError() | |||
|
22 | ||||
|
23 | def timer(self, stat, rate=1): | |||
|
24 | return Timer(self, stat, rate) | |||
|
25 | ||||
|
26 | def timing(self, stat, delta, rate=1): | |||
|
27 | """ | |||
|
28 | Send new timing information. | |||
|
29 | ||||
|
30 | `delta` can be either a number of milliseconds or a timedelta. | |||
|
31 | """ | |||
|
32 | if isinstance(delta, timedelta): | |||
|
33 | # Convert timedelta to number of milliseconds. | |||
|
34 | delta = delta.total_seconds() * 1000. | |||
|
35 | self._send_stat(stat, '%0.6f|ms' % delta, rate) | |||
|
36 | ||||
|
37 | def incr(self, stat, count=1, rate=1): | |||
|
38 | """Increment a stat by `count`.""" | |||
|
39 | self._send_stat(stat, '%s|c' % count, rate) | |||
|
40 | ||||
|
41 | def decr(self, stat, count=1, rate=1): | |||
|
42 | """Decrement a stat by `count`.""" | |||
|
43 | self.incr(stat, -count, rate) | |||
|
44 | ||||
|
45 | def gauge(self, stat, value, rate=1, delta=False): | |||
|
46 | """Set a gauge value.""" | |||
|
47 | if value < 0 and not delta: | |||
|
48 | if rate < 1: | |||
|
49 | if random.random() > rate: | |||
|
50 | return | |||
|
51 | with self.pipeline() as pipe: | |||
|
52 | pipe._send_stat(stat, '0|g', 1) | |||
|
53 | pipe._send_stat(stat, '%s|g' % value, 1) | |||
|
54 | else: | |||
|
55 | prefix = '+' if delta and value >= 0 else '' | |||
|
56 | self._send_stat(stat, '%s%s|g' % (prefix, value), rate) | |||
|
57 | ||||
|
58 | def set(self, stat, value, rate=1): | |||
|
59 | """Set a set value.""" | |||
|
60 | self._send_stat(stat, '%s|s' % value, rate) | |||
|
61 | ||||
|
62 | def _send_stat(self, stat, value, rate): | |||
|
63 | self._after(self._prepare(stat, value, rate)) | |||
|
64 | ||||
|
65 | def _prepare(self, stat, value, rate): | |||
|
66 | if rate < 1: | |||
|
67 | if random.random() > rate: | |||
|
68 | return | |||
|
69 | value = '%s|@%s' % (value, rate) | |||
|
70 | ||||
|
71 | if self._prefix: | |||
|
72 | stat = '%s.%s' % (self._prefix, stat) | |||
|
73 | ||||
|
74 | return '%s:%s' % (stat, value) | |||
|
75 | ||||
|
76 | def _after(self, data): | |||
|
77 | if data: | |||
|
78 | self._send(data) | |||
|
79 | ||||
|
80 | ||||
|
81 | class PipelineBase(StatsClientBase): | |||
|
82 | ||||
|
83 | def __init__(self, client): | |||
|
84 | self._client = client | |||
|
85 | self._prefix = client._prefix | |||
|
86 | self._stats = deque() | |||
|
87 | ||||
|
88 | def _send(self): | |||
|
89 | raise NotImplementedError() | |||
|
90 | ||||
|
91 | def _after(self, data): | |||
|
92 | if data is not None: | |||
|
93 | self._stats.append(data) | |||
|
94 | ||||
|
95 | def __enter__(self): | |||
|
96 | return self | |||
|
97 | ||||
|
98 | def __exit__(self, typ, value, tb): | |||
|
99 | self.send() | |||
|
100 | ||||
|
101 | def send(self): | |||
|
102 | if not self._stats: | |||
|
103 | return | |||
|
104 | self._send() | |||
|
105 | ||||
|
106 | def pipeline(self): | |||
|
107 | return self.__class__(self) |
@@ -0,0 +1,75 | |||||
|
1 | from __future__ import absolute_import, division, unicode_literals | |||
|
2 | ||||
|
3 | import socket | |||
|
4 | ||||
|
5 | from .base import StatsClientBase, PipelineBase | |||
|
6 | ||||
|
7 | ||||
|
8 | class StreamPipeline(PipelineBase): | |||
|
9 | def _send(self): | |||
|
10 | self._client._after('\n'.join(self._stats)) | |||
|
11 | self._stats.clear() | |||
|
12 | ||||
|
13 | ||||
|
14 | class StreamClientBase(StatsClientBase): | |||
|
15 | def connect(self): | |||
|
16 | raise NotImplementedError() | |||
|
17 | ||||
|
18 | def close(self): | |||
|
19 | if self._sock and hasattr(self._sock, 'close'): | |||
|
20 | self._sock.close() | |||
|
21 | self._sock = None | |||
|
22 | ||||
|
23 | def reconnect(self): | |||
|
24 | self.close() | |||
|
25 | self.connect() | |||
|
26 | ||||
|
27 | def pipeline(self): | |||
|
28 | return StreamPipeline(self) | |||
|
29 | ||||
|
30 | def _send(self, data): | |||
|
31 | """Send data to statsd.""" | |||
|
32 | if not self._sock: | |||
|
33 | self.connect() | |||
|
34 | self._do_send(data) | |||
|
35 | ||||
|
36 | def _do_send(self, data): | |||
|
37 | self._sock.sendall(data.encode('ascii') + b'\n') | |||
|
38 | ||||
|
39 | ||||
|
40 | class TCPStatsClient(StreamClientBase): | |||
|
41 | """TCP version of StatsClient.""" | |||
|
42 | ||||
|
43 | def __init__(self, host='localhost', port=8125, prefix=None, | |||
|
44 | timeout=None, ipv6=False): | |||
|
45 | """Create a new client.""" | |||
|
46 | self._host = host | |||
|
47 | self._port = port | |||
|
48 | self._ipv6 = ipv6 | |||
|
49 | self._timeout = timeout | |||
|
50 | self._prefix = prefix | |||
|
51 | self._sock = None | |||
|
52 | ||||
|
53 | def connect(self): | |||
|
54 | fam = socket.AF_INET6 if self._ipv6 else socket.AF_INET | |||
|
55 | family, _, _, _, addr = socket.getaddrinfo( | |||
|
56 | self._host, self._port, fam, socket.SOCK_STREAM)[0] | |||
|
57 | self._sock = socket.socket(family, socket.SOCK_STREAM) | |||
|
58 | self._sock.settimeout(self._timeout) | |||
|
59 | self._sock.connect(addr) | |||
|
60 | ||||
|
61 | ||||
|
62 | class UnixSocketStatsClient(StreamClientBase): | |||
|
63 | """Unix domain socket version of StatsClient.""" | |||
|
64 | ||||
|
65 | def __init__(self, socket_path, prefix=None, timeout=None): | |||
|
66 | """Create a new client.""" | |||
|
67 | self._socket_path = socket_path | |||
|
68 | self._timeout = timeout | |||
|
69 | self._prefix = prefix | |||
|
70 | self._sock = None | |||
|
71 | ||||
|
72 | def connect(self): | |||
|
73 | self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | |||
|
74 | self._sock.settimeout(self._timeout) | |||
|
75 | self._sock.connect(self._socket_path) |
@@ -0,0 +1,71 | |||||
|
1 | from __future__ import absolute_import, division, unicode_literals | |||
|
2 | ||||
|
3 | import functools | |||
|
4 | ||||
|
5 | # Use timer that's not susceptible to time of day adjustments. | |||
|
6 | try: | |||
|
7 | # perf_counter is only present on Py3.3+ | |||
|
8 | from time import perf_counter as time_now | |||
|
9 | except ImportError: | |||
|
10 | # fall back to using time | |||
|
11 | from time import time as time_now | |||
|
12 | ||||
|
13 | ||||
|
14 | def safe_wraps(wrapper, *args, **kwargs): | |||
|
15 | """Safely wraps partial functions.""" | |||
|
16 | while isinstance(wrapper, functools.partial): | |||
|
17 | wrapper = wrapper.func | |||
|
18 | return functools.wraps(wrapper, *args, **kwargs) | |||
|
19 | ||||
|
20 | ||||
|
21 | class Timer(object): | |||
|
22 | """A context manager/decorator for statsd.timing().""" | |||
|
23 | ||||
|
24 | def __init__(self, client, stat, rate=1): | |||
|
25 | self.client = client | |||
|
26 | self.stat = stat | |||
|
27 | self.rate = rate | |||
|
28 | self.ms = None | |||
|
29 | self._sent = False | |||
|
30 | self._start_time = None | |||
|
31 | ||||
|
32 | def __call__(self, f): | |||
|
33 | """Thread-safe timing function decorator.""" | |||
|
34 | @safe_wraps(f) | |||
|
35 | def _wrapped(*args, **kwargs): | |||
|
36 | start_time = time_now() | |||
|
37 | try: | |||
|
38 | return f(*args, **kwargs) | |||
|
39 | finally: | |||
|
40 | elapsed_time_ms = 1000.0 * (time_now() - start_time) | |||
|
41 | self.client.timing(self.stat, elapsed_time_ms, self.rate) | |||
|
42 | return _wrapped | |||
|
43 | ||||
|
44 | def __enter__(self): | |||
|
45 | return self.start() | |||
|
46 | ||||
|
47 | def __exit__(self, typ, value, tb): | |||
|
48 | self.stop() | |||
|
49 | ||||
|
50 | def start(self): | |||
|
51 | self.ms = None | |||
|
52 | self._sent = False | |||
|
53 | self._start_time = time_now() | |||
|
54 | return self | |||
|
55 | ||||
|
56 | def stop(self, send=True): | |||
|
57 | if self._start_time is None: | |||
|
58 | raise RuntimeError('Timer has not started.') | |||
|
59 | dt = time_now() - self._start_time | |||
|
60 | self.ms = 1000.0 * dt # Convert to milliseconds. | |||
|
61 | if send: | |||
|
62 | self.send() | |||
|
63 | return self | |||
|
64 | ||||
|
65 | def send(self): | |||
|
66 | if self.ms is None: | |||
|
67 | raise RuntimeError('No data recorded.') | |||
|
68 | if self._sent: | |||
|
69 | raise RuntimeError('Already sent data.') | |||
|
70 | self._sent = True | |||
|
71 | self.client.timing(self.stat, self.ms, self.rate) |
@@ -0,0 +1,55 | |||||
|
1 | from __future__ import absolute_import, division, unicode_literals | |||
|
2 | ||||
|
3 | import socket | |||
|
4 | ||||
|
5 | from .base import StatsClientBase, PipelineBase | |||
|
6 | ||||
|
7 | ||||
|
8 | class Pipeline(PipelineBase): | |||
|
9 | ||||
|
10 | def __init__(self, client): | |||
|
11 | super(Pipeline, self).__init__(client) | |||
|
12 | self._maxudpsize = client._maxudpsize | |||
|
13 | ||||
|
14 | def _send(self): | |||
|
15 | data = self._stats.popleft() | |||
|
16 | while self._stats: | |||
|
17 | # Use popleft to preserve the order of the stats. | |||
|
18 | stat = self._stats.popleft() | |||
|
19 | if len(stat) + len(data) + 1 >= self._maxudpsize: | |||
|
20 | self._client._after(data) | |||
|
21 | data = stat | |||
|
22 | else: | |||
|
23 | data += '\n' + stat | |||
|
24 | self._client._after(data) | |||
|
25 | ||||
|
26 | ||||
|
27 | class StatsClient(StatsClientBase): | |||
|
28 | """A client for statsd.""" | |||
|
29 | ||||
|
30 | def __init__(self, host='localhost', port=8125, prefix=None, | |||
|
31 | maxudpsize=512, ipv6=False): | |||
|
32 | """Create a new client.""" | |||
|
33 | fam = socket.AF_INET6 if ipv6 else socket.AF_INET | |||
|
34 | family, _, _, _, addr = socket.getaddrinfo( | |||
|
35 | host, port, fam, socket.SOCK_DGRAM)[0] | |||
|
36 | self._addr = addr | |||
|
37 | self._sock = socket.socket(family, socket.SOCK_DGRAM) | |||
|
38 | self._prefix = prefix | |||
|
39 | self._maxudpsize = maxudpsize | |||
|
40 | ||||
|
41 | def _send(self, data): | |||
|
42 | """Send data to statsd.""" | |||
|
43 | try: | |||
|
44 | self._sock.sendto(data.encode('ascii'), self._addr) | |||
|
45 | except (socket.error, RuntimeError): | |||
|
46 | # No time for love, Dr. Jones! | |||
|
47 | pass | |||
|
48 | ||||
|
49 | def close(self): | |||
|
50 | if self._sock and hasattr(self._sock, 'close'): | |||
|
51 | self._sock.close() | |||
|
52 | self._sock = None | |||
|
53 | ||||
|
54 | def pipeline(self): | |||
|
55 | return Pipeline(self) |
@@ -0,0 +1,64 | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | import logging | |||
|
4 | from sqlalchemy import * | |||
|
5 | ||||
|
6 | from alembic.migration import MigrationContext | |||
|
7 | from alembic.operations import Operations | |||
|
8 | ||||
|
9 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
|
10 | from rhodecode.model import meta, init_model_encryption | |||
|
11 | ||||
|
12 | ||||
|
13 | log = logging.getLogger(__name__) | |||
|
14 | ||||
|
15 | ||||
|
16 | def upgrade(migrate_engine): | |||
|
17 | """ | |||
|
18 | Upgrade operations go here. | |||
|
19 | Don't create your own engine; bind migrate_engine to your metadata | |||
|
20 | """ | |||
|
21 | _reset_base(migrate_engine) | |||
|
22 | from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db | |||
|
23 | ||||
|
24 | init_model_encryption(db) | |||
|
25 | ||||
|
26 | # issue fixups | |||
|
27 | fixups(db, meta.Session) | |||
|
28 | ||||
|
29 | ||||
|
30 | def downgrade(migrate_engine): | |||
|
31 | meta = MetaData() | |||
|
32 | meta.bind = migrate_engine | |||
|
33 | ||||
|
34 | ||||
|
35 | def fixups(models, _SESSION): | |||
|
36 | # now create new changed value of clone_url | |||
|
37 | Optional = models.Optional | |||
|
38 | ||||
|
39 | def get_by_name(cls, key): | |||
|
40 | return cls.query().filter(cls.app_settings_name == key).scalar() | |||
|
41 | ||||
|
42 | def create_or_update(cls, key, val=Optional(''), type_=Optional('unicode')): | |||
|
43 | res = get_by_name(cls, key) | |||
|
44 | if not res: | |||
|
45 | val = Optional.extract(val) | |||
|
46 | type_ = Optional.extract(type_) | |||
|
47 | res = cls(key, val, type_) | |||
|
48 | else: | |||
|
49 | res.app_settings_name = key | |||
|
50 | if not isinstance(val, Optional): | |||
|
51 | # update if set | |||
|
52 | res.app_settings_value = val | |||
|
53 | if not isinstance(type_, Optional): | |||
|
54 | # update if set | |||
|
55 | res.app_settings_type = type_ | |||
|
56 | return res | |||
|
57 | ||||
|
58 | clone_uri_tmpl = models.Repository.DEFAULT_CLONE_URI_ID | |||
|
59 | print('settings new clone by url template to %s' % clone_uri_tmpl) | |||
|
60 | ||||
|
61 | sett = create_or_update(models.RhodeCodeSetting, | |||
|
62 | 'clone_uri_id_tmpl', clone_uri_tmpl, 'unicode') | |||
|
63 | _SESSION().add(sett) | |||
|
64 | _SESSION.commit() |
@@ -0,0 +1,32 | |||||
|
1 | ## -*- coding: utf-8 -*- | |||
|
2 | <%inherit file="base.mako"/> | |||
|
3 | ||||
|
4 | <%def name="subject()" filter="n,trim,whitespace_filter"> | |||
|
5 | New Version of RhodeCode is available ! | |||
|
6 | </%def> | |||
|
7 | ||||
|
8 | ## plain text version of the email. Empty by default | |||
|
9 | <%def name="body_plaintext()" filter="n,trim"> | |||
|
10 | A new version of RhodeCode is available! | |||
|
11 | ||||
|
12 | Your version: ${current_ver} | |||
|
13 | New version: ${latest_ver} | |||
|
14 | ||||
|
15 | Release notes: | |||
|
16 | ||||
|
17 | https://docs.rhodecode.com/RhodeCode-Enterprise/release-notes/release-notes-${latest_ver}.html | |||
|
18 | </%def> | |||
|
19 | ||||
|
20 | ## BODY GOES BELOW | |||
|
21 | ||||
|
22 | <h3>A new version of RhodeCode is available!</h3> | |||
|
23 | <br/> | |||
|
24 | Your version: ${current_ver}<br/> | |||
|
25 | New version: <strong>${latest_ver}</strong><br/> | |||
|
26 | ||||
|
27 | <h4>Release notes</h4> | |||
|
28 | ||||
|
29 | <a href="https://docs.rhodecode.com/RhodeCode-Enterprise/release-notes/release-notes-${latest_ver}.html"> | |||
|
30 | https://docs.rhodecode.com/RhodeCode-Enterprise/release-notes/release-notes-${latest_ver}.html | |||
|
31 | </a> | |||
|
32 |
@@ -73,3 +73,5 90734aac31ee4563bbe665a43ff73190cc762275 | |||||
73 | a9655707f7cf4146affc51c12fe5ed8e02898a57 v4.23.0 |
|
73 | a9655707f7cf4146affc51c12fe5ed8e02898a57 v4.23.0 | |
74 | 56310d93b33b97535908ef9c7b0985b89bb7fad2 v4.23.1 |
|
74 | 56310d93b33b97535908ef9c7b0985b89bb7fad2 v4.23.1 | |
75 | 7637c38528fa38c1eabc1fde6a869c20995a0da7 v4.23.2 |
|
75 | 7637c38528fa38c1eabc1fde6a869c20995a0da7 v4.23.2 | |
|
76 | 6aeb4ac3ef7f0ac699c914740dad3688c9495e83 v4.24.0 | |||
|
77 | 6eaf953da06e468a4c4e5239d3d0e700bda6b163 v4.24.1 |
@@ -1,4 +1,4 | |||||
1 |
|RCE| 4.2 |
|
1 | |RCE| 4.24.0 |RNS| | |
2 | ------------------ |
|
2 | ------------------ | |
3 |
|
3 | |||
4 | Release Date |
|
4 | Release Date | |
@@ -16,14 +16,16 New Features | |||||
16 | Can be used for backups etc. |
|
16 | Can be used for backups etc. | |
17 | - Pull requests: expose commit versions in the pull-request commit list. |
|
17 | - Pull requests: expose commit versions in the pull-request commit list. | |
18 |
|
18 | |||
|
19 | ||||
19 | General |
|
20 | General | |
20 | ^^^^^^^ |
|
21 | ^^^^^^^ | |
21 |
|
22 | |||
22 | - Deps: bumped redis to 3.5.3 |
|
23 | - Deps: bumped redis to 3.5.3 | |
23 |
- |
|
24 | - Rcextensions: improve examples for some usage. | |
24 | - Setup: added optional parameters to apply a default license, or skip re-creation of database at install. |
|
25 | - Setup: added optional parameters to apply a default license, or skip re-creation of database at install. | |
25 | - Docs: update headers for NGINX |
|
26 | - Docs: update headers for NGINX | |
26 | - Beaker cache: remove no longer used beaker cache init |
|
27 | - Beaker cache: remove no longer used beaker cache init | |
|
28 | - Installation: the installer no longer requires gzip and bzip packages, and works on python 2 and 3 | |||
27 |
|
29 | |||
28 |
|
30 | |||
29 | Security |
|
31 | Security |
@@ -9,6 +9,7 Release Notes | |||||
9 | .. toctree:: |
|
9 | .. toctree:: | |
10 | :maxdepth: 1 |
|
10 | :maxdepth: 1 | |
11 |
|
11 | |||
|
12 | release-notes-4.24.1.rst | |||
12 | release-notes-4.24.0.rst |
|
13 | release-notes-4.24.0.rst | |
13 | release-notes-4.23.2.rst |
|
14 | release-notes-4.23.2.rst | |
14 | release-notes-4.23.1.rst |
|
15 | release-notes-4.23.1.rst |
@@ -274,6 +274,12 self: super: { | |||||
274 | ]; |
|
274 | ]; | |
275 | }); |
|
275 | }); | |
276 |
|
276 | |||
|
277 | "pytest" = super."pytest".override (attrs: { | |||
|
278 | patches = [ | |||
|
279 | ./patches/pytest/setuptools.patch | |||
|
280 | ]; | |||
|
281 | }); | |||
|
282 | ||||
277 | # Avoid that base packages screw up the build process |
|
283 | # Avoid that base packages screw up the build process | |
278 | inherit (basePythonPackages) |
|
284 | inherit (basePythonPackages) | |
279 | setuptools; |
|
285 | setuptools; |
@@ -48,7 +48,7 PYRAMID_SETTINGS = {} | |||||
48 | EXTENSIONS = {} |
|
48 | EXTENSIONS = {} | |
49 |
|
49 | |||
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) | |
51 |
__dbversion__ = 11 |
|
51 | __dbversion__ = 113 # defines current db version for migrations | |
52 | __platform__ = platform.system() |
|
52 | __platform__ = platform.system() | |
53 | __license__ = 'AGPLv3, and Commercial License' |
|
53 | __license__ = 'AGPLv3, and Commercial License' | |
54 | __author__ = 'RhodeCode GmbH' |
|
54 | __author__ = 'RhodeCode GmbH' |
@@ -384,6 +384,7 class AdminSettingsView(BaseAppView): | |||||
384 | ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'), |
|
384 | ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'), | |
385 | ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'), |
|
385 | ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'), | |
386 | ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'), |
|
386 | ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'), | |
|
387 | ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'), | |||
387 | ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'), |
|
388 | ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'), | |
388 | ('support_url', 'rhodecode_support_url', 'unicode'), |
|
389 | ('support_url', 'rhodecode_support_url', 'unicode'), | |
389 | ('show_revision_number', 'rhodecode_show_revision_number', 'bool'), |
|
390 | ('show_revision_number', 'rhodecode_show_revision_number', 'bool'), |
@@ -102,6 +102,11 Check if we should use full-topic or min | |||||
102 | 'date': datetime.datetime.now(), |
|
102 | 'date': datetime.datetime.now(), | |
103 | }, |
|
103 | }, | |
104 |
|
104 | |||
|
105 | 'update_available': { | |||
|
106 | 'current_ver': '4.23.0', | |||
|
107 | 'latest_ver': '4.24.0', | |||
|
108 | }, | |||
|
109 | ||||
105 | 'exception': { |
|
110 | 'exception': { | |
106 | 'email_prefix': '[RHODECODE ERROR]', |
|
111 | 'email_prefix': '[RHODECODE ERROR]', | |
107 | 'exc_id': exc_traceback['exc_id'], |
|
112 | 'exc_id': exc_traceback['exc_id'], |
@@ -420,6 +420,27 class TestPullrequestsView(object): | |||||
420 | assert pull_request.title == 'New title' |
|
420 | assert pull_request.title == 'New title' | |
421 | assert pull_request.description == 'New description' |
|
421 | assert pull_request.description == 'New description' | |
422 |
|
422 | |||
|
423 | def test_edit_title_description(self, pr_util, csrf_token): | |||
|
424 | pull_request = pr_util.create_pull_request() | |||
|
425 | pull_request_id = pull_request.pull_request_id | |||
|
426 | ||||
|
427 | response = self.app.post( | |||
|
428 | route_path('pullrequest_update', | |||
|
429 | repo_name=pull_request.target_repo.repo_name, | |||
|
430 | pull_request_id=pull_request_id), | |||
|
431 | params={ | |||
|
432 | 'edit_pull_request': 'true', | |||
|
433 | 'title': 'New title {} {2} {foo}', | |||
|
434 | 'description': 'New description', | |||
|
435 | 'csrf_token': csrf_token}) | |||
|
436 | ||||
|
437 | assert_session_flash( | |||
|
438 | response, u'Pull request title & description updated.', | |||
|
439 | category='success') | |||
|
440 | ||||
|
441 | pull_request = PullRequest.get(pull_request_id) | |||
|
442 | assert pull_request.title_safe == 'New title {{}} {{2}} {{foo}}' | |||
|
443 | ||||
423 | def test_edit_title_description_closed(self, pr_util, csrf_token): |
|
444 | def test_edit_title_description_closed(self, pr_util, csrf_token): | |
424 | pull_request = pr_util.create_pull_request() |
|
445 | pull_request = pr_util.create_pull_request() | |
425 | pull_request_id = pull_request.pull_request_id |
|
446 | pull_request_id = pull_request.pull_request_id |
@@ -83,14 +83,10 class RepoSummaryView(RepoAppView): | |||||
83 | if self._rhodecode_user.username != User.DEFAULT_USER: |
|
83 | if self._rhodecode_user.username != User.DEFAULT_USER: | |
84 | username = safe_str(self._rhodecode_user.username) |
|
84 | username = safe_str(self._rhodecode_user.username) | |
85 |
|
85 | |||
86 |
_def_clone_uri |
|
86 | _def_clone_uri = c.clone_uri_tmpl | |
|
87 | _def_clone_uri_id = c.clone_uri_id_tmpl | |||
87 | _def_clone_uri_ssh = c.clone_uri_ssh_tmpl |
|
88 | _def_clone_uri_ssh = c.clone_uri_ssh_tmpl | |
88 |
|
89 | |||
89 | if '{repo}' in _def_clone_uri: |
|
|||
90 | _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}') |
|
|||
91 | elif '{repoid}' in _def_clone_uri: |
|
|||
92 | _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}') |
|
|||
93 |
|
||||
94 | c.clone_repo_url = self.db_repo.clone_url( |
|
90 | c.clone_repo_url = self.db_repo.clone_url( | |
95 | user=username, uri_tmpl=_def_clone_uri) |
|
91 | user=username, uri_tmpl=_def_clone_uri) | |
96 | c.clone_repo_url_id = self.db_repo.clone_url( |
|
92 | c.clone_repo_url_id = self.db_repo.clone_url( |
@@ -340,6 +340,10 def includeme(config, auth_resources=Non | |||||
340 | 'rhodecode.lib.request_counter.get_request_counter', |
|
340 | 'rhodecode.lib.request_counter.get_request_counter', | |
341 | 'request_count') |
|
341 | 'request_count') | |
342 |
|
342 | |||
|
343 | config.add_request_method( | |||
|
344 | 'rhodecode.lib._vendor.statsd.get_statsd_client', | |||
|
345 | 'statsd', reify=True) | |||
|
346 | ||||
343 | # Set the authorization policy. |
|
347 | # Set the authorization policy. | |
344 | authz_policy = ACLAuthorizationPolicy() |
|
348 | authz_policy = ACLAuthorizationPolicy() | |
345 | config.set_authorization_policy(authz_policy) |
|
349 | config.set_authorization_policy(authz_policy) |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
@@ -342,6 +342,7 def attach_context_attributes(context, r | |||||
342 | if request.GET.get('default_encoding'): |
|
342 | if request.GET.get('default_encoding'): | |
343 | context.default_encodings.insert(0, request.GET.get('default_encoding')) |
|
343 | context.default_encodings.insert(0, request.GET.get('default_encoding')) | |
344 | context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl') |
|
344 | context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl') | |
|
345 | context.clone_uri_id_tmpl = rc_config.get('rhodecode_clone_uri_id_tmpl') | |||
345 | context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl') |
|
346 | context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl') | |
346 |
|
347 | |||
347 | # INI stored |
|
348 | # INI stored |
@@ -33,9 +33,9 from email.utils import formatdate | |||||
33 |
|
33 | |||
34 | import rhodecode |
|
34 | import rhodecode | |
35 | from rhodecode.lib import audit_logger |
|
35 | from rhodecode.lib import audit_logger | |
36 | from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask |
|
36 | from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask, run_task | |
37 | from rhodecode.lib import hooks_base |
|
37 | from rhodecode.lib import hooks_base | |
38 | from rhodecode.lib.utils2 import safe_int, str2bool |
|
38 | from rhodecode.lib.utils2 import safe_int, str2bool, aslist | |
39 | from rhodecode.model.db import ( |
|
39 | from rhodecode.model.db import ( | |
40 | Session, IntegrityError, true, Repository, RepoGroup, User) |
|
40 | Session, IntegrityError, true, Repository, RepoGroup, User) | |
41 |
|
41 | |||
@@ -338,15 +338,39 def repo_maintenance(repoid): | |||||
338 |
|
338 | |||
339 |
|
339 | |||
340 | @async_task(ignore_result=True) |
|
340 | @async_task(ignore_result=True) | |
341 | def check_for_update(): |
|
341 | def check_for_update(send_email_notification=True, email_recipients=None): | |
342 | from rhodecode.model.update import UpdateModel |
|
342 | from rhodecode.model.update import UpdateModel | |
|
343 | from rhodecode.model.notification import EmailNotificationModel | |||
|
344 | ||||
|
345 | log = get_logger(check_for_update) | |||
343 | update_url = UpdateModel().get_update_url() |
|
346 | update_url = UpdateModel().get_update_url() | |
344 | cur_ver = rhodecode.__version__ |
|
347 | cur_ver = rhodecode.__version__ | |
345 |
|
348 | |||
346 | try: |
|
349 | try: | |
347 | data = UpdateModel().get_update_data(update_url) |
|
350 | data = UpdateModel().get_update_data(update_url) | |
348 | latest = data['versions'][0] |
|
351 | ||
349 |
UpdateModel().store_version( |
|
352 | current_ver = UpdateModel().get_stored_version(fallback=cur_ver) | |
|
353 | latest_ver = data['versions'][0]['version'] | |||
|
354 | UpdateModel().store_version(latest_ver) | |||
|
355 | ||||
|
356 | if send_email_notification: | |||
|
357 | log.debug('Send email notification is enabled. ' | |||
|
358 | 'Current RhodeCode version: %s, latest known: %s', current_ver, latest_ver) | |||
|
359 | if UpdateModel().is_outdated(current_ver, latest_ver): | |||
|
360 | ||||
|
361 | email_kwargs = { | |||
|
362 | 'current_ver': current_ver, | |||
|
363 | 'latest_ver': latest_ver, | |||
|
364 | } | |||
|
365 | ||||
|
366 | (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email( | |||
|
367 | EmailNotificationModel.TYPE_UPDATE_AVAILABLE, **email_kwargs) | |||
|
368 | ||||
|
369 | email_recipients = aslist(email_recipients, sep=',') or \ | |||
|
370 |