##// END OF EJS Templates
release: merge back stable branch into default
milka -
r4642:cced0269 merge default
parent child Browse files
Show More
@@ -0,0 +1,46 b''
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 b''
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 b''
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 b''
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 b''
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 b''
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 b''
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 b''
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 b''
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
@@ -1,75 +1,77 b''
1 1 1bd3e92b7e2e2d2024152b34bb88dff1db544a71 v4.0.0
2 2 170c5398320ea6cddd50955e88d408794c21d43a v4.0.1
3 3 c3fe200198f5aa34cf2e4066df2881a9cefe3704 v4.1.0
4 4 7fd5c850745e2ea821fb4406af5f4bff9b0a7526 v4.1.1
5 5 41c87da28a179953df86061d817bc35533c66dd2 v4.1.2
6 6 baaf9f5bcea3bae0ef12ae20c8b270482e62abb6 v4.2.0
7 7 32a70c7e56844a825f61df496ee5eaf8c3c4e189 v4.2.1
8 8 fa695cdb411d294679ac081d595ac654e5613b03 v4.3.0
9 9 0e4dc11b58cad833c513fe17bac39e6850edf959 v4.3.1
10 10 8a876f48f5cb1d018b837db28ff928500cb32cfb v4.4.0
11 11 8dd86b410b1aac086ffdfc524ef300f896af5047 v4.4.1
12 12 d2514226abc8d3b4f6fb57765f47d1b6fb360a05 v4.4.2
13 13 27d783325930af6dad2741476c0d0b1b7c8415c2 v4.5.0
14 14 7f2016f352abcbdba4a19d4039c386e9629449da v4.5.1
15 15 416fec799314c70a5c780fb28b3357b08869333a v4.5.2
16 16 27c3b85fafc83143e6678fbc3da69e1615bcac55 v4.6.0
17 17 5ad13deb9118c2a5243d4032d4d9cc174e5872db v4.6.1
18 18 2be921e01fa24bb102696ada596f87464c3666f6 v4.7.0
19 19 7198bdec29c2872c974431d55200d0398354cdb1 v4.7.1
20 20 bd1c8d230fe741c2dfd7100a0ef39fd0774fd581 v4.7.2
21 21 9731914f89765d9628dc4dddc84bc9402aa124c8 v4.8.0
22 22 c5a2b7d0e4bbdebc4a62d7b624befe375207b659 v4.9.0
23 23 d9aa3b27ac9f7e78359775c75fedf7bfece232f1 v4.9.1
24 24 4ba4d74981cec5d6b28b158f875a2540952c2f74 v4.10.0
25 25 0a6821cbd6b0b3c21503002f88800679fa35ab63 v4.10.1
26 26 434ad90ec8d621f4416074b84f6e9ce03964defb v4.10.2
27 27 68baee10e698da2724c6e0f698c03a6abb993bf2 v4.10.3
28 28 00821d3afd1dce3f4767cc353f84a17f7d5218a1 v4.10.4
29 29 22f6744ad8cc274311825f63f953e4dee2ea5cb9 v4.10.5
30 30 96eb24bea2f5f9258775245e3f09f6fa0a4dda01 v4.10.6
31 31 3121217a812c956d7dd5a5875821bd73e8002a32 v4.11.0
32 32 fa98b454715ac5b912f39e84af54345909a2a805 v4.11.1
33 33 3982abcfdcc229a723cebe52d3a9bcff10bba08e v4.11.2
34 34 33195f145db9172f0a8f1487e09207178a6ab065 v4.11.3
35 35 194c74f33e32bbae6fc4d71ec5a999cff3c13605 v4.11.4
36 36 8fbd8b0c3ddc2fa4ac9e4ca16942a03eb593df2d v4.11.5
37 37 f0609aa5d5d05a1ca2f97c3995542236131c9d8a v4.11.6
38 38 b5b30547d90d2e088472a70c84878f429ffbf40d v4.12.0
39 39 9072253aa8894d20c00b4a43dc61c2168c1eff94 v4.12.1
40 40 6a517543ea9ef9987d74371bd2a315eb0b232dc9 v4.12.2
41 41 7fc0731b024c3114be87865eda7ab621cc957e32 v4.12.3
42 42 6d531c0b068c6eda62dddceedc9f845ecb6feb6f v4.12.4
43 43 3d6bf2d81b1564830eb5e83396110d2a9a93eb1e v4.13.0
44 44 5468fc89e708bd90e413cd0d54350017abbdbc0e v4.13.1
45 45 610d621550521c314ee97b3d43473ac0bcf06fb8 v4.13.2
46 46 7dc62c090881fb5d03268141e71e0940d7c3295d v4.13.3
47 47 9151328c1c46b72ba6f00d7640d9141e75aa1ca2 v4.14.0
48 48 a47eeac5dfa41fa6779d90452affba4091c3ade8 v4.14.1
49 49 4b34ce0d2c3c10510626b3b65044939bb7a2cddf v4.15.0
50 50 14502561d22e6b70613674cd675ae9a604b7989f v4.15.1
51 51 4aaa40b605b01af78a9f6882eca561c54b525ef0 v4.15.2
52 52 797744642eca86640ed20bef2cd77445780abaec v4.16.0
53 53 6c3452c7c25ed35ff269690929e11960ed6ad7d3 v4.16.1
54 54 5d8057df561c4b6b81b6401aed7d2f911e6e77f7 v4.16.2
55 55 13acfc008896ef4c62546bab5074e8f6f89b4fa7 v4.17.0
56 56 45b9b610976f483877142fe75321808ce9ebac59 v4.17.1
57 57 ad5bd0c4bd322fdbd04bb825a3d027e08f7a3901 v4.17.2
58 58 037f5794b55a6236d68f6485a485372dde6566e0 v4.17.3
59 59 83bc3100cfd6094c1d04f475ddb299b7dc3d0b33 v4.17.4
60 60 e3de8c95baf8cc9109ca56aee8193a2cb6a54c8a v4.17.4
61 61 f37a3126570477543507f0bc9d245ce75546181a v4.18.0
62 62 71d8791463e87b64c1a18475de330ee600d37561 v4.18.1
63 63 4bd6b75dac1d25c64885d4d49385e5533f21c525 v4.18.2
64 64 12ed92fe57f2e9fc7b71dc0b65e26c2da5c7085f v4.18.3
65 65 ddef396a6567117de531d67d44c739cbbfc3eebb v4.19.0
66 66 c0c65acd73914bf4368222d510afe1161ab8c07c v4.19.1
67 67 7ac623a4a2405917e2af660d645ded662011e40d v4.19.2
68 68 ef7ffda65eeb90c3ba88590a6cb816ef9b0bc232 v4.19.3
69 69 3e635489bb7961df93b01e42454ad1a8730ae968 v4.20.0
70 70 7e2eb896a02ca7cd2cd9f0f853ef3dac3f0039e3 v4.20.1
71 71 8bb5fece08ab65986225b184e46f53d2a71729cb v4.21.0
72 72 90734aac31ee4563bbe665a43ff73190cc762275 v4.22.0
73 73 a9655707f7cf4146affc51c12fe5ed8e02898a57 v4.23.0
74 74 56310d93b33b97535908ef9c7b0985b89bb7fad2 v4.23.1
75 75 7637c38528fa38c1eabc1fde6a869c20995a0da7 v4.23.2
76 6aeb4ac3ef7f0ac699c914740dad3688c9495e83 v4.24.0
77 6eaf953da06e468a4c4e5239d3d0e700bda6b163 v4.24.1
@@ -1,54 +1,56 b''
1 |RCE| 4.23.0 |RNS|
1 |RCE| 4.24.0 |RNS|
2 2 ------------------
3 3
4 4 Release Date
5 5 ^^^^^^^^^^^^
6 6
7 7 - 2021-01-10
8 8
9 9
10 10 New Features
11 11 ^^^^^^^^^^^^
12 12
13 13 - Artifacts: expose additional headers, and content-disposition for downloads from artifacts exposing the real name of the file.
14 14 - Token access: allow token in headers not only in GET/URL.
15 15 - File-store: added a stream upload endpoint, it allows to upload GBs of data into artifact store efficiently.
16 16 Can be used for backups etc.
17 17 - Pull requests: expose commit versions in the pull-request commit list.
18 18
19
19 20 General
20 21 ^^^^^^^
21 22
22 23 - Deps: bumped redis to 3.5.3
23 - rcextensions: improve examples
24 - Rcextensions: improve examples for some usage.
24 25 - Setup: added optional parameters to apply a default license, or skip re-creation of database at install.
25 26 - Docs: update headers for NGINX
26 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 31 Security
30 32 ^^^^^^^^
31 33
32 34
33 35
34 36 Performance
35 37 ^^^^^^^^^^^
36 38
37 39 - Core: speed up cache loading on application startup.
38 40 - Core: allow loading all auth plugins in once place for CE/EE code.
39 41 - Application: not use config.scan(), and replace all @add_view decorator into a explicit add_view call for faster app start.
40 42
41 43
42 44 Fixes
43 45 ^^^^^
44 46
45 47 - Svn: don't print exceptions in case of safe calls
46 48 - Vcsserver: use safer maxfd reporting, some linux systems get a problem with this
47 49 - Hooks-daemon: fixed problem with lost hooks value from .ini file.
48 50 - Exceptions: fixed truncated exception text
49 51
50 52
51 53 Upgrade notes
52 54 ^^^^^^^^^^^^^
53 55
54 56 - Scheduled release 4.24.0
@@ -1,152 +1,153 b''
1 1 .. _rhodecode-release-notes-ref:
2 2
3 3 Release Notes
4 4 =============
5 5
6 6 |RCE| 4.x Versions
7 7 ------------------
8 8
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.24.1.rst
12 13 release-notes-4.24.0.rst
13 14 release-notes-4.23.2.rst
14 15 release-notes-4.23.1.rst
15 16 release-notes-4.23.0.rst
16 17 release-notes-4.22.0.rst
17 18 release-notes-4.21.0.rst
18 19 release-notes-4.20.1.rst
19 20 release-notes-4.20.0.rst
20 21 release-notes-4.19.3.rst
21 22 release-notes-4.19.2.rst
22 23 release-notes-4.19.1.rst
23 24 release-notes-4.19.0.rst
24 25 release-notes-4.18.3.rst
25 26 release-notes-4.18.2.rst
26 27 release-notes-4.18.1.rst
27 28 release-notes-4.18.0.rst
28 29 release-notes-4.17.4.rst
29 30 release-notes-4.17.3.rst
30 31 release-notes-4.17.2.rst
31 32 release-notes-4.17.1.rst
32 33 release-notes-4.17.0.rst
33 34 release-notes-4.16.2.rst
34 35 release-notes-4.16.1.rst
35 36 release-notes-4.16.0.rst
36 37 release-notes-4.15.2.rst
37 38 release-notes-4.15.1.rst
38 39 release-notes-4.15.0.rst
39 40 release-notes-4.14.1.rst
40 41 release-notes-4.14.0.rst
41 42 release-notes-4.13.3.rst
42 43 release-notes-4.13.2.rst
43 44 release-notes-4.13.1.rst
44 45 release-notes-4.13.0.rst
45 46 release-notes-4.12.4.rst
46 47 release-notes-4.12.3.rst
47 48 release-notes-4.12.2.rst
48 49 release-notes-4.12.1.rst
49 50 release-notes-4.12.0.rst
50 51 release-notes-4.11.6.rst
51 52 release-notes-4.11.5.rst
52 53 release-notes-4.11.4.rst
53 54 release-notes-4.11.3.rst
54 55 release-notes-4.11.2.rst
55 56 release-notes-4.11.1.rst
56 57 release-notes-4.11.0.rst
57 58 release-notes-4.10.6.rst
58 59 release-notes-4.10.5.rst
59 60 release-notes-4.10.4.rst
60 61 release-notes-4.10.3.rst
61 62 release-notes-4.10.2.rst
62 63 release-notes-4.10.1.rst
63 64 release-notes-4.10.0.rst
64 65 release-notes-4.9.1.rst
65 66 release-notes-4.9.0.rst
66 67 release-notes-4.8.0.rst
67 68 release-notes-4.7.2.rst
68 69 release-notes-4.7.1.rst
69 70 release-notes-4.7.0.rst
70 71 release-notes-4.6.1.rst
71 72 release-notes-4.6.0.rst
72 73 release-notes-4.5.2.rst
73 74 release-notes-4.5.1.rst
74 75 release-notes-4.5.0.rst
75 76 release-notes-4.4.2.rst
76 77 release-notes-4.4.1.rst
77 78 release-notes-4.4.0.rst
78 79 release-notes-4.3.1.rst
79 80 release-notes-4.3.0.rst
80 81 release-notes-4.2.1.rst
81 82 release-notes-4.2.0.rst
82 83 release-notes-4.1.2.rst
83 84 release-notes-4.1.1.rst
84 85 release-notes-4.1.0.rst
85 86 release-notes-4.0.1.rst
86 87 release-notes-4.0.0.rst
87 88
88 89 |RCE| 3.x Versions
89 90 ------------------
90 91
91 92 .. toctree::
92 93 :maxdepth: 1
93 94
94 95 release-notes-3.8.4.rst
95 96 release-notes-3.8.3.rst
96 97 release-notes-3.8.2.rst
97 98 release-notes-3.8.1.rst
98 99 release-notes-3.8.0.rst
99 100 release-notes-3.7.1.rst
100 101 release-notes-3.7.0.rst
101 102 release-notes-3.6.1.rst
102 103 release-notes-3.6.0.rst
103 104 release-notes-3.5.2.rst
104 105 release-notes-3.5.1.rst
105 106 release-notes-3.5.0.rst
106 107 release-notes-3.4.1.rst
107 108 release-notes-3.4.0.rst
108 109 release-notes-3.3.4.rst
109 110 release-notes-3.3.3.rst
110 111 release-notes-3.3.2.rst
111 112 release-notes-3.3.1.rst
112 113 release-notes-3.3.0.rst
113 114 release-notes-3.2.3.rst
114 115 release-notes-3.2.2.rst
115 116 release-notes-3.2.1.rst
116 117 release-notes-3.2.0.rst
117 118 release-notes-3.1.1.rst
118 119 release-notes-3.1.0.rst
119 120 release-notes-3.0.2.rst
120 121 release-notes-3.0.1.rst
121 122 release-notes-3.0.0.rst
122 123
123 124 |RCE| 2.x Versions
124 125 ------------------
125 126
126 127 .. toctree::
127 128 :maxdepth: 1
128 129
129 130 release-notes-2.2.8.rst
130 131 release-notes-2.2.7.rst
131 132 release-notes-2.2.6.rst
132 133 release-notes-2.2.5.rst
133 134 release-notes-2.2.4.rst
134 135 release-notes-2.2.3.rst
135 136 release-notes-2.2.2.rst
136 137 release-notes-2.2.1.rst
137 138 release-notes-2.2.0.rst
138 139 release-notes-2.1.0.rst
139 140 release-notes-2.0.2.rst
140 141 release-notes-2.0.1.rst
141 142 release-notes-2.0.0.rst
142 143
143 144 |RCE| 1.x Versions
144 145 ------------------
145 146
146 147 .. toctree::
147 148 :maxdepth: 1
148 149
149 150 release-notes-1.7.2.rst
150 151 release-notes-1.7.1.rst
151 152 release-notes-1.7.0.rst
152 153 release-notes-1.6.0.rst
@@ -1,281 +1,287 b''
1 1 # Overrides for the generated python-packages.nix
2 2 #
3 3 # This function is intended to be used as an extension to the generated file
4 4 # python-packages.nix. The main objective is to add needed dependencies of C
5 5 # libraries and tweak the build instructions where needed.
6 6
7 7 { pkgs
8 8 , basePythonPackages
9 9 }:
10 10
11 11 let
12 12 sed = "sed -i";
13 13
14 14 localLicenses = {
15 15 repoze = {
16 16 fullName = "Repoze License";
17 17 url = http://www.repoze.org/LICENSE.txt;
18 18 };
19 19 };
20 20
21 21 in
22 22
23 23 self: super: {
24 24
25 25 "appenlight-client" = super."appenlight-client".override (attrs: {
26 26 meta = {
27 27 license = [ pkgs.lib.licenses.bsdOriginal ];
28 28 };
29 29 });
30 30
31 31 "beaker" = super."beaker".override (attrs: {
32 32 patches = [
33 33 ./patches/beaker/patch-beaker-lock-func-debug.diff
34 34 ./patches/beaker/patch-beaker-metadata-reuse.diff
35 35 ./patches/beaker/patch-beaker-improved-redis.diff
36 36 ./patches/beaker/patch-beaker-improved-redis-2.diff
37 37 ];
38 38 });
39 39
40 40 "cffi" = super."cffi".override (attrs: {
41 41 buildInputs = [
42 42 pkgs.libffi
43 43 ];
44 44 });
45 45
46 46 "cryptography" = super."cryptography".override (attrs: {
47 47 buildInputs = [
48 48 pkgs.openssl
49 49 ];
50 50 });
51 51
52 52 "gevent" = super."gevent".override (attrs: {
53 53 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
54 54 # NOTE: (marcink) odd requirements from gevent aren't set properly,
55 55 # thus we need to inject psutil manually
56 56 self."psutil"
57 57 ];
58 58 });
59 59
60 60 "future" = super."future".override (attrs: {
61 61 meta = {
62 62 license = [ pkgs.lib.licenses.mit ];
63 63 };
64 64 });
65 65
66 66 "testpath" = super."testpath".override (attrs: {
67 67 meta = {
68 68 license = [ pkgs.lib.licenses.mit ];
69 69 };
70 70 });
71 71
72 72 "gnureadline" = super."gnureadline".override (attrs: {
73 73 buildInputs = [
74 74 pkgs.ncurses
75 75 ];
76 76 patchPhase = ''
77 77 substituteInPlace setup.py --replace "/bin/bash" "${pkgs.bash}/bin/bash"
78 78 '';
79 79 });
80 80
81 81 "gunicorn" = super."gunicorn".override (attrs: {
82 82 propagatedBuildInputs = [
83 83 # johbo: futures is needed as long as we are on Python 2, otherwise
84 84 # gunicorn explodes if used with multiple threads per worker.
85 85 self."futures"
86 86 ];
87 87 });
88 88
89 89 "nbconvert" = super."nbconvert".override (attrs: {
90 90 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
91 91 # marcink: plug in jupyter-client for notebook rendering
92 92 self."jupyter-client"
93 93 ];
94 94 });
95 95
96 96 "ipython" = super."ipython".override (attrs: {
97 97 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
98 98 self."gnureadline"
99 99 ];
100 100 });
101 101
102 102 "lxml" = super."lxml".override (attrs: {
103 103 buildInputs = [
104 104 pkgs.libxml2
105 105 pkgs.libxslt
106 106 ];
107 107 propagatedBuildInputs = [
108 108 # Needed, so that "setup.py bdist_wheel" does work
109 109 self."wheel"
110 110 ];
111 111 });
112 112
113 113 "mysql-python" = super."mysql-python".override (attrs: {
114 114 buildInputs = [
115 115 pkgs.openssl
116 116 ];
117 117 propagatedBuildInputs = [
118 118 pkgs.libmysql
119 119 pkgs.zlib
120 120 ];
121 121 });
122 122
123 123 "psycopg2" = super."psycopg2".override (attrs: {
124 124 propagatedBuildInputs = [
125 125 pkgs.postgresql
126 126 ];
127 127 meta = {
128 128 license = pkgs.lib.licenses.lgpl3Plus;
129 129 };
130 130 });
131 131
132 132 "pycurl" = super."pycurl".override (attrs: {
133 133 propagatedBuildInputs = [
134 134 pkgs.curl
135 135 pkgs.openssl
136 136 ];
137 137
138 138 preConfigure = ''
139 139 substituteInPlace setup.py --replace '--static-libs' '--libs'
140 140 export PYCURL_SSL_LIBRARY=openssl
141 141 '';
142 142
143 143 meta = {
144 144 license = pkgs.lib.licenses.mit;
145 145 };
146 146 });
147 147
148 148 "pyramid" = super."pyramid".override (attrs: {
149 149 meta = {
150 150 license = localLicenses.repoze;
151 151 };
152 152 });
153 153
154 154 "pyramid-debugtoolbar" = super."pyramid-debugtoolbar".override (attrs: {
155 155 meta = {
156 156 license = [ pkgs.lib.licenses.bsdOriginal localLicenses.repoze ];
157 157 };
158 158 });
159 159
160 160 "pysqlite" = super."pysqlite".override (attrs: {
161 161 propagatedBuildInputs = [
162 162 pkgs.sqlite
163 163 ];
164 164 meta = {
165 165 license = [ pkgs.lib.licenses.zlib pkgs.lib.licenses.libpng ];
166 166 };
167 167 });
168 168
169 169 "python-ldap" = super."python-ldap".override (attrs: {
170 170 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
171 171 pkgs.openldap
172 172 pkgs.cyrus_sasl
173 173 pkgs.openssl
174 174 ];
175 175 });
176 176
177 177 "python-pam" = super."python-pam".override (attrs: {
178 178 propagatedBuildInputs = [
179 179 pkgs.pam
180 180 ];
181 181
182 182 # TODO: johbo: Check if this can be avoided, or transform into
183 183 # a real patch
184 184 patchPhase = ''
185 185 substituteInPlace pam.py \
186 186 --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"'
187 187 '';
188 188
189 189 });
190 190
191 191 "python-saml" = super."python-saml".override (attrs: {
192 192 buildInputs = [
193 193 pkgs.libxml2
194 194 pkgs.libxslt
195 195 ];
196 196 });
197 197
198 198 "dm.xmlsec.binding" = super."dm.xmlsec.binding".override (attrs: {
199 199 buildInputs = [
200 200 pkgs.libxml2
201 201 pkgs.libxslt
202 202 pkgs.xmlsec
203 203 pkgs.libtool
204 204 ];
205 205 });
206 206
207 207 "pyzmq" = super."pyzmq".override (attrs: {
208 208 buildInputs = [
209 209 pkgs.czmq
210 210 ];
211 211 });
212 212
213 213 "urlobject" = super."urlobject".override (attrs: {
214 214 meta = {
215 215 license = {
216 216 spdxId = "Unlicense";
217 217 fullName = "The Unlicense";
218 218 url = http://unlicense.org/;
219 219 };
220 220 };
221 221 });
222 222
223 223 "docutils" = super."docutils".override (attrs: {
224 224 meta = {
225 225 license = pkgs.lib.licenses.bsd2;
226 226 };
227 227 });
228 228
229 229 "colander" = super."colander".override (attrs: {
230 230 meta = {
231 231 license = localLicenses.repoze;
232 232 };
233 233 });
234 234
235 235 "pyramid-beaker" = super."pyramid-beaker".override (attrs: {
236 236 meta = {
237 237 license = localLicenses.repoze;
238 238 };
239 239 });
240 240
241 241 "pyramid-mako" = super."pyramid-mako".override (attrs: {
242 242 meta = {
243 243 license = localLicenses.repoze;
244 244 };
245 245 });
246 246
247 247 "repoze.lru" = super."repoze.lru".override (attrs: {
248 248 meta = {
249 249 license = localLicenses.repoze;
250 250 };
251 251 });
252 252
253 253 "python-editor" = super."python-editor".override (attrs: {
254 254 meta = {
255 255 license = pkgs.lib.licenses.asl20;
256 256 };
257 257 });
258 258
259 259 "translationstring" = super."translationstring".override (attrs: {
260 260 meta = {
261 261 license = localLicenses.repoze;
262 262 };
263 263 });
264 264
265 265 "venusian" = super."venusian".override (attrs: {
266 266 meta = {
267 267 license = localLicenses.repoze;
268 268 };
269 269 });
270 270
271 271 "supervisor" = super."supervisor".override (attrs: {
272 272 patches = [
273 273 ./patches/supervisor/patch-rlimits-old-kernel.diff
274 274 ];
275 275 });
276 276
277 "pytest" = super."pytest".override (attrs: {
278 patches = [
279 ./patches/pytest/setuptools.patch
280 ];
281 });
282
277 283 # Avoid that base packages screw up the build process
278 284 inherit (basePythonPackages)
279 285 setuptools;
280 286
281 287 }
@@ -1,60 +1,60 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 from collections import OrderedDict
23 23
24 24 import sys
25 25 import platform
26 26
27 27 VERSION = tuple(open(os.path.join(
28 28 os.path.dirname(__file__), 'VERSION')).read().split('.'))
29 29
30 30 BACKENDS = OrderedDict()
31 31
32 32 BACKENDS['hg'] = 'Mercurial repository'
33 33 BACKENDS['git'] = 'Git repository'
34 34 BACKENDS['svn'] = 'Subversion repository'
35 35
36 36
37 37 CELERY_ENABLED = False
38 38 CELERY_EAGER = False
39 39
40 40 # link to config for pyramid
41 41 CONFIG = {}
42 42
43 43 # Populated with the settings dictionary from application init in
44 44 # rhodecode.conf.environment.load_pyramid_environment
45 45 PYRAMID_SETTINGS = {}
46 46
47 47 # Linked module for extensions
48 48 EXTENSIONS = {}
49 49
50 50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
51 __dbversion__ = 112 # defines current db version for migrations
51 __dbversion__ = 113 # defines current db version for migrations
52 52 __platform__ = platform.system()
53 53 __license__ = 'AGPLv3, and Commercial License'
54 54 __author__ = 'RhodeCode GmbH'
55 55 __url__ = 'https://code.rhodecode.com'
56 56
57 57 is_windows = __platform__ in ['Windows']
58 58 is_unix = not is_windows
59 59 is_test = False
60 60 disable_error_handler = False
@@ -1,719 +1,720 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import collections
24 24
25 25 import datetime
26 26 import formencode
27 27 import formencode.htmlfill
28 28
29 29 import rhodecode
30 30
31 31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34
35 35 from rhodecode.apps._base import BaseAppView
36 36 from rhodecode.apps._base.navigation import navigation_list
37 37 from rhodecode.apps.svn_support.config_keys import generate_config
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 41 from rhodecode.lib.celerylib import tasks, run_task
42 42 from rhodecode.lib.utils import repo2db_mapper
43 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 44 from rhodecode.lib.index import searcher_from_config
45 45
46 46 from rhodecode.model.db import RhodeCodeUi, Repository
47 47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 49 LabsSettingsForm, IssueTrackerPatternsForm)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52
53 53 from rhodecode.model.scm import ScmModel
54 54 from rhodecode.model.notification import EmailNotificationModel
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.settings import (
57 57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
58 58 SettingsModel)
59 59
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class AdminSettingsView(BaseAppView):
65 65
66 66 def load_default_context(self):
67 67 c = self._get_local_tmpl_context()
68 68 c.labs_active = str2bool(
69 69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
70 70 c.navlist = navigation_list(self.request)
71 71 return c
72 72
73 73 @classmethod
74 74 def _get_ui_settings(cls):
75 75 ret = RhodeCodeUi.query().all()
76 76
77 77 if not ret:
78 78 raise Exception('Could not get application ui settings !')
79 79 settings = {}
80 80 for each in ret:
81 81 k = each.ui_key
82 82 v = each.ui_value
83 83 if k == '/':
84 84 k = 'root_path'
85 85
86 86 if k in ['push_ssl', 'publish', 'enabled']:
87 87 v = str2bool(v)
88 88
89 89 if k.find('.') != -1:
90 90 k = k.replace('.', '_')
91 91
92 92 if each.ui_section in ['hooks', 'extensions']:
93 93 v = each.ui_active
94 94
95 95 settings[each.ui_section + '_' + k] = v
96 96 return settings
97 97
98 98 @classmethod
99 99 def _form_defaults(cls):
100 100 defaults = SettingsModel().get_all_settings()
101 101 defaults.update(cls._get_ui_settings())
102 102
103 103 defaults.update({
104 104 'new_svn_branch': '',
105 105 'new_svn_tag': '',
106 106 })
107 107 return defaults
108 108
109 109 @LoginRequired()
110 110 @HasPermissionAllDecorator('hg.admin')
111 111 def settings_vcs(self):
112 112 c = self.load_default_context()
113 113 c.active = 'vcs'
114 114 model = VcsSettingsModel()
115 115 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
116 116 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
117 117
118 118 settings = self.request.registry.settings
119 119 c.svn_proxy_generate_config = settings[generate_config]
120 120
121 121 defaults = self._form_defaults()
122 122
123 123 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
124 124
125 125 data = render('rhodecode:templates/admin/settings/settings.mako',
126 126 self._get_template_context(c), self.request)
127 127 html = formencode.htmlfill.render(
128 128 data,
129 129 defaults=defaults,
130 130 encoding="UTF-8",
131 131 force_defaults=False
132 132 )
133 133 return Response(html)
134 134
135 135 @LoginRequired()
136 136 @HasPermissionAllDecorator('hg.admin')
137 137 @CSRFRequired()
138 138 def settings_vcs_update(self):
139 139 _ = self.request.translate
140 140 c = self.load_default_context()
141 141 c.active = 'vcs'
142 142
143 143 model = VcsSettingsModel()
144 144 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
145 145 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
146 146
147 147 settings = self.request.registry.settings
148 148 c.svn_proxy_generate_config = settings[generate_config]
149 149
150 150 application_form = ApplicationUiSettingsForm(self.request.translate)()
151 151
152 152 try:
153 153 form_result = application_form.to_python(dict(self.request.POST))
154 154 except formencode.Invalid as errors:
155 155 h.flash(
156 156 _("Some form inputs contain invalid data."),
157 157 category='error')
158 158 data = render('rhodecode:templates/admin/settings/settings.mako',
159 159 self._get_template_context(c), self.request)
160 160 html = formencode.htmlfill.render(
161 161 data,
162 162 defaults=errors.value,
163 163 errors=errors.error_dict or {},
164 164 prefix_error=False,
165 165 encoding="UTF-8",
166 166 force_defaults=False
167 167 )
168 168 return Response(html)
169 169
170 170 try:
171 171 if c.visual.allow_repo_location_change:
172 172 model.update_global_path_setting(form_result['paths_root_path'])
173 173
174 174 model.update_global_ssl_setting(form_result['web_push_ssl'])
175 175 model.update_global_hook_settings(form_result)
176 176
177 177 model.create_or_update_global_svn_settings(form_result)
178 178 model.create_or_update_global_hg_settings(form_result)
179 179 model.create_or_update_global_git_settings(form_result)
180 180 model.create_or_update_global_pr_settings(form_result)
181 181 except Exception:
182 182 log.exception("Exception while updating settings")
183 183 h.flash(_('Error occurred during updating '
184 184 'application settings'), category='error')
185 185 else:
186 186 Session().commit()
187 187 h.flash(_('Updated VCS settings'), category='success')
188 188 raise HTTPFound(h.route_path('admin_settings_vcs'))
189 189
190 190 data = render('rhodecode:templates/admin/settings/settings.mako',
191 191 self._get_template_context(c), self.request)
192 192 html = formencode.htmlfill.render(
193 193 data,
194 194 defaults=self._form_defaults(),
195 195 encoding="UTF-8",
196 196 force_defaults=False
197 197 )
198 198 return Response(html)
199 199
200 200 @LoginRequired()
201 201 @HasPermissionAllDecorator('hg.admin')
202 202 @CSRFRequired()
203 203 def settings_vcs_delete_svn_pattern(self):
204 204 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
205 205 model = VcsSettingsModel()
206 206 try:
207 207 model.delete_global_svn_pattern(delete_pattern_id)
208 208 except SettingNotFound:
209 209 log.exception(
210 210 'Failed to delete svn_pattern with id %s', delete_pattern_id)
211 211 raise HTTPNotFound()
212 212
213 213 Session().commit()
214 214 return True
215 215
216 216 @LoginRequired()
217 217 @HasPermissionAllDecorator('hg.admin')
218 218 def settings_mapping(self):
219 219 c = self.load_default_context()
220 220 c.active = 'mapping'
221 221
222 222 data = render('rhodecode:templates/admin/settings/settings.mako',
223 223 self._get_template_context(c), self.request)
224 224 html = formencode.htmlfill.render(
225 225 data,
226 226 defaults=self._form_defaults(),
227 227 encoding="UTF-8",
228 228 force_defaults=False
229 229 )
230 230 return Response(html)
231 231
232 232 @LoginRequired()
233 233 @HasPermissionAllDecorator('hg.admin')
234 234 @CSRFRequired()
235 235 def settings_mapping_update(self):
236 236 _ = self.request.translate
237 237 c = self.load_default_context()
238 238 c.active = 'mapping'
239 239 rm_obsolete = self.request.POST.get('destroy', False)
240 240 invalidate_cache = self.request.POST.get('invalidate', False)
241 241 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
242 242
243 243 if invalidate_cache:
244 244 log.debug('invalidating all repositories cache')
245 245 for repo in Repository.get_all():
246 246 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
247 247
248 248 filesystem_repos = ScmModel().repo_scan()
249 249 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
250 250 PermissionModel().trigger_permission_flush()
251 251
252 252 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
253 253 h.flash(_('Repositories successfully '
254 254 'rescanned added: %s ; removed: %s') %
255 255 (_repr(added), _repr(removed)),
256 256 category='success')
257 257 raise HTTPFound(h.route_path('admin_settings_mapping'))
258 258
259 259 @LoginRequired()
260 260 @HasPermissionAllDecorator('hg.admin')
261 261 def settings_global(self):
262 262 c = self.load_default_context()
263 263 c.active = 'global'
264 264 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 265 .get_personal_group_name_pattern()
266 266
267 267 data = render('rhodecode:templates/admin/settings/settings.mako',
268 268 self._get_template_context(c), self.request)
269 269 html = formencode.htmlfill.render(
270 270 data,
271 271 defaults=self._form_defaults(),
272 272 encoding="UTF-8",
273 273 force_defaults=False
274 274 )
275 275 return Response(html)
276 276
277 277 @LoginRequired()
278 278 @HasPermissionAllDecorator('hg.admin')
279 279 @CSRFRequired()
280 280 def settings_global_update(self):
281 281 _ = self.request.translate
282 282 c = self.load_default_context()
283 283 c.active = 'global'
284 284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 285 .get_personal_group_name_pattern()
286 286 application_form = ApplicationSettingsForm(self.request.translate)()
287 287 try:
288 288 form_result = application_form.to_python(dict(self.request.POST))
289 289 except formencode.Invalid as errors:
290 290 h.flash(
291 291 _("Some form inputs contain invalid data."),
292 292 category='error')
293 293 data = render('rhodecode:templates/admin/settings/settings.mako',
294 294 self._get_template_context(c), self.request)
295 295 html = formencode.htmlfill.render(
296 296 data,
297 297 defaults=errors.value,
298 298 errors=errors.error_dict or {},
299 299 prefix_error=False,
300 300 encoding="UTF-8",
301 301 force_defaults=False
302 302 )
303 303 return Response(html)
304 304
305 305 settings = [
306 306 ('title', 'rhodecode_title', 'unicode'),
307 307 ('realm', 'rhodecode_realm', 'unicode'),
308 308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 309 ('post_code', 'rhodecode_post_code', 'unicode'),
310 310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 314 ]
315 315 try:
316 316 for setting, form_key, type_ in settings:
317 317 sett = SettingsModel().create_or_update_setting(
318 318 setting, form_result[form_key], type_)
319 319 Session().add(sett)
320 320
321 321 Session().commit()
322 322 SettingsModel().invalidate_settings_cache()
323 323 h.flash(_('Updated application settings'), category='success')
324 324 except Exception:
325 325 log.exception("Exception while updating application settings")
326 326 h.flash(
327 327 _('Error occurred during updating application settings'),
328 328 category='error')
329 329
330 330 raise HTTPFound(h.route_path('admin_settings_global'))
331 331
332 332 @LoginRequired()
333 333 @HasPermissionAllDecorator('hg.admin')
334 334 def settings_visual(self):
335 335 c = self.load_default_context()
336 336 c.active = 'visual'
337 337
338 338 data = render('rhodecode:templates/admin/settings/settings.mako',
339 339 self._get_template_context(c), self.request)
340 340 html = formencode.htmlfill.render(
341 341 data,
342 342 defaults=self._form_defaults(),
343 343 encoding="UTF-8",
344 344 force_defaults=False
345 345 )
346 346 return Response(html)
347 347
348 348 @LoginRequired()
349 349 @HasPermissionAllDecorator('hg.admin')
350 350 @CSRFRequired()
351 351 def settings_visual_update(self):
352 352 _ = self.request.translate
353 353 c = self.load_default_context()
354 354 c.active = 'visual'
355 355 application_form = ApplicationVisualisationForm(self.request.translate)()
356 356 try:
357 357 form_result = application_form.to_python(dict(self.request.POST))
358 358 except formencode.Invalid as errors:
359 359 h.flash(
360 360 _("Some form inputs contain invalid data."),
361 361 category='error')
362 362 data = render('rhodecode:templates/admin/settings/settings.mako',
363 363 self._get_template_context(c), self.request)
364 364 html = formencode.htmlfill.render(
365 365 data,
366 366 defaults=errors.value,
367 367 errors=errors.error_dict or {},
368 368 prefix_error=False,
369 369 encoding="UTF-8",
370 370 force_defaults=False
371 371 )
372 372 return Response(html)
373 373
374 374 try:
375 375 settings = [
376 376 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
377 377 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
378 378 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
379 379 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
380 380 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
381 381 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
382 382 ('show_version', 'rhodecode_show_version', 'bool'),
383 383 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
384 384 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
385 385 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
386 386 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
387 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
387 388 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
388 389 ('support_url', 'rhodecode_support_url', 'unicode'),
389 390 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
390 391 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
391 392 ]
392 393 for setting, form_key, type_ in settings:
393 394 sett = SettingsModel().create_or_update_setting(
394 395 setting, form_result[form_key], type_)
395 396 Session().add(sett)
396 397
397 398 Session().commit()
398 399 SettingsModel().invalidate_settings_cache()
399 400 h.flash(_('Updated visualisation settings'), category='success')
400 401 except Exception:
401 402 log.exception("Exception updating visualization settings")
402 403 h.flash(_('Error occurred during updating '
403 404 'visualisation settings'),
404 405 category='error')
405 406
406 407 raise HTTPFound(h.route_path('admin_settings_visual'))
407 408
408 409 @LoginRequired()
409 410 @HasPermissionAllDecorator('hg.admin')
410 411 def settings_issuetracker(self):
411 412 c = self.load_default_context()
412 413 c.active = 'issuetracker'
413 414 defaults = c.rc_config
414 415
415 416 entry_key = 'rhodecode_issuetracker_pat_'
416 417
417 418 c.issuetracker_entries = {}
418 419 for k, v in defaults.items():
419 420 if k.startswith(entry_key):
420 421 uid = k[len(entry_key):]
421 422 c.issuetracker_entries[uid] = None
422 423
423 424 for uid in c.issuetracker_entries:
424 425 c.issuetracker_entries[uid] = AttributeDict({
425 426 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
426 427 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
427 428 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
428 429 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
429 430 })
430 431
431 432 return self._get_template_context(c)
432 433
433 434 @LoginRequired()
434 435 @HasPermissionAllDecorator('hg.admin')
435 436 @CSRFRequired()
436 437 def settings_issuetracker_test(self):
437 438 error_container = []
438 439
439 440 urlified_commit = h.urlify_commit_message(
440 441 self.request.POST.get('test_text', ''),
441 442 'repo_group/test_repo1', error_container=error_container)
442 443 if error_container:
443 444 def converter(inp):
444 445 return h.html_escape(unicode(inp))
445 446
446 447 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
447 448
448 449 return urlified_commit
449 450
450 451 @LoginRequired()
451 452 @HasPermissionAllDecorator('hg.admin')
452 453 @CSRFRequired()
453 454 def settings_issuetracker_update(self):
454 455 _ = self.request.translate
455 456 self.load_default_context()
456 457 settings_model = IssueTrackerSettingsModel()
457 458
458 459 try:
459 460 form = IssueTrackerPatternsForm(self.request.translate)()
460 461 data = form.to_python(self.request.POST)
461 462 except formencode.Invalid as errors:
462 463 log.exception('Failed to add new pattern')
463 464 error = errors
464 465 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
465 466 category='error')
466 467 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
467 468
468 469 if data:
469 470 for uid in data.get('delete_patterns', []):
470 471 settings_model.delete_entries(uid)
471 472
472 473 for pattern in data.get('patterns', []):
473 474 for setting, value, type_ in pattern:
474 475 sett = settings_model.create_or_update_setting(
475 476 setting, value, type_)
476 477 Session().add(sett)
477 478
478 479 Session().commit()
479 480
480 481 SettingsModel().invalidate_settings_cache()
481 482 h.flash(_('Updated issue tracker entries'), category='success')
482 483 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
483 484
484 485 @LoginRequired()
485 486 @HasPermissionAllDecorator('hg.admin')
486 487 @CSRFRequired()
487 488 def settings_issuetracker_delete(self):
488 489 _ = self.request.translate
489 490 self.load_default_context()
490 491 uid = self.request.POST.get('uid')
491 492 try:
492 493 IssueTrackerSettingsModel().delete_entries(uid)
493 494 except Exception:
494 495 log.exception('Failed to delete issue tracker setting %s', uid)
495 496 raise HTTPNotFound()
496 497
497 498 SettingsModel().invalidate_settings_cache()
498 499 h.flash(_('Removed issue tracker entry.'), category='success')
499 500
500 501 return {'deleted': uid}
501 502
502 503 @LoginRequired()
503 504 @HasPermissionAllDecorator('hg.admin')
504 505 def settings_email(self):
505 506 c = self.load_default_context()
506 507 c.active = 'email'
507 508 c.rhodecode_ini = rhodecode.CONFIG
508 509
509 510 data = render('rhodecode:templates/admin/settings/settings.mako',
510 511 self._get_template_context(c), self.request)
511 512 html = formencode.htmlfill.render(
512 513 data,
513 514 defaults=self._form_defaults(),
514 515 encoding="UTF-8",
515 516 force_defaults=False
516 517 )
517 518 return Response(html)
518 519
519 520 @LoginRequired()
520 521 @HasPermissionAllDecorator('hg.admin')
521 522 @CSRFRequired()
522 523 def settings_email_update(self):
523 524 _ = self.request.translate
524 525 c = self.load_default_context()
525 526 c.active = 'email'
526 527
527 528 test_email = self.request.POST.get('test_email')
528 529
529 530 if not test_email:
530 531 h.flash(_('Please enter email address'), category='error')
531 532 raise HTTPFound(h.route_path('admin_settings_email'))
532 533
533 534 email_kwargs = {
534 535 'date': datetime.datetime.now(),
535 536 'user': self._rhodecode_db_user
536 537 }
537 538
538 539 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
539 540 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
540 541
541 542 recipients = [test_email] if test_email else None
542 543
543 544 run_task(tasks.send_email, recipients, subject,
544 545 email_body_plaintext, email_body)
545 546
546 547 h.flash(_('Send email task created'), category='success')
547 548 raise HTTPFound(h.route_path('admin_settings_email'))
548 549
549 550 @LoginRequired()
550 551 @HasPermissionAllDecorator('hg.admin')
551 552 def settings_hooks(self):
552 553 c = self.load_default_context()
553 554 c.active = 'hooks'
554 555
555 556 model = SettingsModel()
556 557 c.hooks = model.get_builtin_hooks()
557 558 c.custom_hooks = model.get_custom_hooks()
558 559
559 560 data = render('rhodecode:templates/admin/settings/settings.mako',
560 561 self._get_template_context(c), self.request)
561 562 html = formencode.htmlfill.render(
562 563 data,
563 564 defaults=self._form_defaults(),
564 565 encoding="UTF-8",
565 566 force_defaults=False
566 567 )
567 568 return Response(html)
568 569
569 570 @LoginRequired()
570 571 @HasPermissionAllDecorator('hg.admin')
571 572 @CSRFRequired()
572 573 def settings_hooks_update(self):
573 574 _ = self.request.translate
574 575 c = self.load_default_context()
575 576 c.active = 'hooks'
576 577 if c.visual.allow_custom_hooks_settings:
577 578 ui_key = self.request.POST.get('new_hook_ui_key')
578 579 ui_value = self.request.POST.get('new_hook_ui_value')
579 580
580 581 hook_id = self.request.POST.get('hook_id')
581 582 new_hook = False
582 583
583 584 model = SettingsModel()
584 585 try:
585 586 if ui_value and ui_key:
586 587 model.create_or_update_hook(ui_key, ui_value)
587 588 h.flash(_('Added new hook'), category='success')
588 589 new_hook = True
589 590 elif hook_id:
590 591 RhodeCodeUi.delete(hook_id)
591 592 Session().commit()
592 593
593 594 # check for edits
594 595 update = False
595 596 _d = self.request.POST.dict_of_lists()
596 597 for k, v in zip(_d.get('hook_ui_key', []),
597 598 _d.get('hook_ui_value_new', [])):
598 599 model.create_or_update_hook(k, v)
599 600 update = True
600 601
601 602 if update and not new_hook:
602 603 h.flash(_('Updated hooks'), category='success')
603 604 Session().commit()
604 605 except Exception:
605 606 log.exception("Exception during hook creation")
606 607 h.flash(_('Error occurred during hook creation'),
607 608 category='error')
608 609
609 610 raise HTTPFound(h.route_path('admin_settings_hooks'))
610 611
611 612 @LoginRequired()
612 613 @HasPermissionAllDecorator('hg.admin')
613 614 def settings_search(self):
614 615 c = self.load_default_context()
615 616 c.active = 'search'
616 617
617 618 c.searcher = searcher_from_config(self.request.registry.settings)
618 619 c.statistics = c.searcher.statistics(self.request.translate)
619 620
620 621 return self._get_template_context(c)
621 622
622 623 @LoginRequired()
623 624 @HasPermissionAllDecorator('hg.admin')
624 625 def settings_automation(self):
625 626 c = self.load_default_context()
626 627 c.active = 'automation'
627 628
628 629 return self._get_template_context(c)
629 630
630 631 @LoginRequired()
631 632 @HasPermissionAllDecorator('hg.admin')
632 633 def settings_labs(self):
633 634 c = self.load_default_context()
634 635 if not c.labs_active:
635 636 raise HTTPFound(h.route_path('admin_settings'))
636 637
637 638 c.active = 'labs'
638 639 c.lab_settings = _LAB_SETTINGS
639 640
640 641 data = render('rhodecode:templates/admin/settings/settings.mako',
641 642 self._get_template_context(c), self.request)
642 643 html = formencode.htmlfill.render(
643 644 data,
644 645 defaults=self._form_defaults(),
645 646 encoding="UTF-8",
646 647 force_defaults=False
647 648 )
648 649 return Response(html)
649 650
650 651 @LoginRequired()
651 652 @HasPermissionAllDecorator('hg.admin')
652 653 @CSRFRequired()
653 654 def settings_labs_update(self):
654 655 _ = self.request.translate
655 656 c = self.load_default_context()
656 657 c.active = 'labs'
657 658
658 659 application_form = LabsSettingsForm(self.request.translate)()
659 660 try:
660 661 form_result = application_form.to_python(dict(self.request.POST))
661 662 except formencode.Invalid as errors:
662 663 h.flash(
663 664 _("Some form inputs contain invalid data."),
664 665 category='error')
665 666 data = render('rhodecode:templates/admin/settings/settings.mako',
666 667 self._get_template_context(c), self.request)
667 668 html = formencode.htmlfill.render(
668 669 data,
669 670 defaults=errors.value,
670 671 errors=errors.error_dict or {},
671 672 prefix_error=False,
672 673 encoding="UTF-8",
673 674 force_defaults=False
674 675 )
675 676 return Response(html)
676 677
677 678 try:
678 679 session = Session()
679 680 for setting in _LAB_SETTINGS:
680 681 setting_name = setting.key[len('rhodecode_'):]
681 682 sett = SettingsModel().create_or_update_setting(
682 683 setting_name, form_result[setting.key], setting.type)
683 684 session.add(sett)
684 685
685 686 except Exception:
686 687 log.exception('Exception while updating lab settings')
687 688 h.flash(_('Error occurred during updating labs settings'),
688 689 category='error')
689 690 else:
690 691 Session().commit()
691 692 SettingsModel().invalidate_settings_cache()
692 693 h.flash(_('Updated Labs settings'), category='success')
693 694 raise HTTPFound(h.route_path('admin_settings_labs'))
694 695
695 696 data = render('rhodecode:templates/admin/settings/settings.mako',
696 697 self._get_template_context(c), self.request)
697 698 html = formencode.htmlfill.render(
698 699 data,
699 700 defaults=self._form_defaults(),
700 701 encoding="UTF-8",
701 702 force_defaults=False
702 703 )
703 704 return Response(html)
704 705
705 706
706 707 # :param key: name of the setting including the 'rhodecode_' prefix
707 708 # :param type: the RhodeCodeSetting type to use.
708 709 # :param group: the i18ned group in which we should dispaly this setting
709 710 # :param label: the i18ned label we should display for this setting
710 711 # :param help: the i18ned help we should dispaly for this setting
711 712 LabSetting = collections.namedtuple(
712 713 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
713 714
714 715
715 716 # This list has to be kept in sync with the form
716 717 # rhodecode.model.forms.LabsSettingsForm.
717 718 _LAB_SETTINGS = [
718 719
719 720 ]
@@ -1,471 +1,476 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import logging
23 23 import datetime
24 24
25 25 from pyramid.renderers import render_to_response
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode.lib.celerylib import run_task, tasks
28 28 from rhodecode.lib.utils2 import AttributeDict
29 29 from rhodecode.model.db import User
30 30 from rhodecode.model.notification import EmailNotificationModel
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class DebugStyleView(BaseAppView):
36 36
37 37 def load_default_context(self):
38 38 c = self._get_local_tmpl_context()
39 39 return c
40 40
41 41 def index(self):
42 42 c = self.load_default_context()
43 43 c.active = 'index'
44 44
45 45 return render_to_response(
46 46 'debug_style/index.html', self._get_template_context(c),
47 47 request=self.request)
48 48
49 49 def render_email(self):
50 50 c = self.load_default_context()
51 51 email_id = self.request.matchdict['email_id']
52 52 c.active = 'emails'
53 53
54 54 pr = AttributeDict(
55 55 pull_request_id=123,
56 56 title='digital_ocean: fix redis, elastic search start on boot, '
57 57 'fix fd limits on supervisor, set postgres 11 version',
58 58 description='''
59 59 Check if we should use full-topic or mini-topic.
60 60
61 61 - full topic produces some problems with merge states etc
62 62 - server-mini-topic needs probably tweeks.
63 63 ''',
64 64 repo_name='foobar',
65 65 source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
66 66 target_ref_parts=AttributeDict(type='branch', name='master'),
67 67 )
68 68
69 69 target_repo = AttributeDict(repo_name='repo_group/target_repo')
70 70 source_repo = AttributeDict(repo_name='repo_group/source_repo')
71 71 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
72 72 # file/commit changes for PR update
73 73 commit_changes = AttributeDict({
74 74 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
75 75 'removed': ['eeeeeeeeeee'],
76 76 })
77 77
78 78 file_changes = AttributeDict({
79 79 'added': ['a/file1.md', 'file2.py'],
80 80 'modified': ['b/modified_file.rst'],
81 81 'removed': ['.idea'],
82 82 })
83 83
84 84 exc_traceback = {
85 85 'exc_utc_date': '2020-03-26T12:54:50.683281',
86 86 'exc_id': 139638856342656,
87 87 'exc_timestamp': '1585227290.683288',
88 88 'version': 'v1',
89 89 'exc_message': 'Traceback (most recent call last):\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/tweens.py", line 41, in excview_tween\n response = handler(request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/router.py", line 148, in handle_request\n registry, request, context, context_iface, view_name\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/view.py", line 667, in _call_view\n response = view_callable(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 188, in attr_view\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 214, in predicate_wrapper\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 401, in viewresult_to_response\n result = view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 132, in _class_view\n response = getattr(inst, attr)()\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/apps/debug_style/views.py", line 355, in render_email\n template_type, **email_kwargs.get(email_id, {}))\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/model/notification.py", line 402, in render_email\n body = email_template.render(None, **_kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 95, in render\n return self._render_with_exc(tmpl, args, kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 79, in _render_with_exc\n return render_func.render(*args, **kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/template.py", line 476, in render\n return runtime._render(self, self.callable_, args, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 883, in _render\n **_kwargs_for_callable(callable_, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 920, in _render_context\n _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 947, in _exec_template\n callable_(context, *args, **kwargs)\n File "rhodecode_templates_email_templates_base_mako", line 63, in render_body\n File "rhodecode_templates_email_templates_exception_tracker_mako", line 43, in render_body\nAttributeError: \'str\' object has no attribute \'get\'\n',
90 90 'exc_type': 'AttributeError'
91 91 }
92 92
93 93 email_kwargs = {
94 94 'test': {},
95 95
96 96 'message': {
97 97 'body': 'message body !'
98 98 },
99 99
100 100 'email_test': {
101 101 'user': user,
102 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 110 'exception': {
106 111 'email_prefix': '[RHODECODE ERROR]',
107 112 'exc_id': exc_traceback['exc_id'],
108 113 'exc_url': 'http://server-url/{}'.format(exc_traceback['exc_id']),
109 114 'exc_type_name': 'NameError',
110 115 'exc_traceback': exc_traceback,
111 116 },
112 117
113 118 'password_reset': {
114 119 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
115 120
116 121 'user': user,
117 122 'date': datetime.datetime.now(),
118 123 'email': 'test@rhodecode.com',
119 124 'first_admin_email': User.get_first_super_admin().email
120 125 },
121 126
122 127 'password_reset_confirmation': {
123 128 'new_password': 'new-password-example',
124 129 'user': user,
125 130 'date': datetime.datetime.now(),
126 131 'email': 'test@rhodecode.com',
127 132 'first_admin_email': User.get_first_super_admin().email
128 133 },
129 134
130 135 'registration': {
131 136 'user': user,
132 137 'date': datetime.datetime.now(),
133 138 },
134 139
135 140 'pull_request_comment': {
136 141 'user': user,
137 142
138 143 'status_change': None,
139 144 'status_change_type': None,
140 145
141 146 'pull_request': pr,
142 147 'pull_request_commits': [],
143 148
144 149 'pull_request_target_repo': target_repo,
145 150 'pull_request_target_repo_url': 'http://target-repo/url',
146 151
147 152 'pull_request_source_repo': source_repo,
148 153 'pull_request_source_repo_url': 'http://source-repo/url',
149 154
150 155 'pull_request_url': 'http://localhost/pr1',
151 156 'pr_comment_url': 'http://comment-url',
152 157 'pr_comment_reply_url': 'http://comment-url#reply',
153 158
154 159 'comment_file': None,
155 160 'comment_line': None,
156 161 'comment_type': 'note',
157 162 'comment_body': 'This is my comment body. *I like !*',
158 163 'comment_id': 2048,
159 164 'renderer_type': 'markdown',
160 165 'mention': True,
161 166
162 167 },
163 168
164 169 'pull_request_comment+status': {
165 170 'user': user,
166 171
167 172 'status_change': 'approved',
168 173 'status_change_type': 'approved',
169 174
170 175 'pull_request': pr,
171 176 'pull_request_commits': [],
172 177
173 178 'pull_request_target_repo': target_repo,
174 179 'pull_request_target_repo_url': 'http://target-repo/url',
175 180
176 181 'pull_request_source_repo': source_repo,
177 182 'pull_request_source_repo_url': 'http://source-repo/url',
178 183
179 184 'pull_request_url': 'http://localhost/pr1',
180 185 'pr_comment_url': 'http://comment-url',
181 186 'pr_comment_reply_url': 'http://comment-url#reply',
182 187
183 188 'comment_type': 'todo',
184 189 'comment_file': None,
185 190 'comment_line': None,
186 191 'comment_body': '''
187 192 I think something like this would be better
188 193
189 194 ```py
190 195 // markdown renderer
191 196
192 197 def db():
193 198 global connection
194 199 return connection
195 200
196 201 ```
197 202
198 203 ''',
199 204 'comment_id': 2048,
200 205 'renderer_type': 'markdown',
201 206 'mention': True,
202 207
203 208 },
204 209
205 210 'pull_request_comment+file': {
206 211 'user': user,
207 212
208 213 'status_change': None,
209 214 'status_change_type': None,
210 215
211 216 'pull_request': pr,
212 217 'pull_request_commits': [],
213 218
214 219 'pull_request_target_repo': target_repo,
215 220 'pull_request_target_repo_url': 'http://target-repo/url',
216 221
217 222 'pull_request_source_repo': source_repo,
218 223 'pull_request_source_repo_url': 'http://source-repo/url',
219 224
220 225 'pull_request_url': 'http://localhost/pr1',
221 226
222 227 'pr_comment_url': 'http://comment-url',
223 228 'pr_comment_reply_url': 'http://comment-url#reply',
224 229
225 230 'comment_file': 'rhodecode/model/get_flow_commits',
226 231 'comment_line': 'o1210',
227 232 'comment_type': 'todo',
228 233 'comment_body': '''
229 234 I like this !
230 235
231 236 But please check this code
232 237
233 238 .. code-block:: javascript
234 239
235 240 // THIS IS RST CODE
236 241
237 242 this.createResolutionComment = function(commentId) {
238 243 // hide the trigger text
239 244 $('#resolve-comment-{0}'.format(commentId)).hide();
240 245
241 246 var comment = $('#comment-'+commentId);
242 247 var commentData = comment.data();
243 248 if (commentData.commentInline) {
244 249 this.createComment(comment, f_path, line_no, commentId)
245 250 } else {
246 251 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
247 252 }
248 253
249 254 return false;
250 255 };
251 256
252 257 This should work better !
253 258 ''',
254 259 'comment_id': 2048,
255 260 'renderer_type': 'rst',
256 261 'mention': True,
257 262
258 263 },
259 264
260 265 'pull_request_update': {
261 266 'updating_user': user,
262 267
263 268 'status_change': None,
264 269 'status_change_type': None,
265 270
266 271 'pull_request': pr,
267 272 'pull_request_commits': [],
268 273
269 274 'pull_request_target_repo': target_repo,
270 275 'pull_request_target_repo_url': 'http://target-repo/url',
271 276
272 277 'pull_request_source_repo': source_repo,
273 278 'pull_request_source_repo_url': 'http://source-repo/url',
274 279
275 280 'pull_request_url': 'http://localhost/pr1',
276 281
277 282 # update comment links
278 283 'pr_comment_url': 'http://comment-url',
279 284 'pr_comment_reply_url': 'http://comment-url#reply',
280 285 'ancestor_commit_id': 'f39bd443',
281 286 'added_commits': commit_changes.added,
282 287 'removed_commits': commit_changes.removed,
283 288 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
284 289 'added_files': file_changes.added,
285 290 'modified_files': file_changes.modified,
286 291 'removed_files': file_changes.removed,
287 292 },
288 293
289 294 'cs_comment': {
290 295 'user': user,
291 296 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
292 297 'status_change': None,
293 298 'status_change_type': None,
294 299
295 300 'commit_target_repo_url': 'http://foo.example.com/#comment1',
296 301 'repo_name': 'test-repo',
297 302 'comment_type': 'note',
298 303 'comment_file': None,
299 304 'comment_line': None,
300 305 'commit_comment_url': 'http://comment-url',
301 306 'commit_comment_reply_url': 'http://comment-url#reply',
302 307 'comment_body': 'This is my comment body. *I like !*',
303 308 'comment_id': 2048,
304 309 'renderer_type': 'markdown',
305 310 'mention': True,
306 311 },
307 312
308 313 'cs_comment+status': {
309 314 'user': user,
310 315 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
311 316 'status_change': 'approved',
312 317 'status_change_type': 'approved',
313 318
314 319 'commit_target_repo_url': 'http://foo.example.com/#comment1',
315 320 'repo_name': 'test-repo',
316 321 'comment_type': 'note',
317 322 'comment_file': None,
318 323 'comment_line': None,
319 324 'commit_comment_url': 'http://comment-url',
320 325 'commit_comment_reply_url': 'http://comment-url#reply',
321 326 'comment_body': '''
322 327 Hello **world**
323 328
324 329 This is a multiline comment :)
325 330
326 331 - list
327 332 - list2
328 333 ''',
329 334 'comment_id': 2048,
330 335 'renderer_type': 'markdown',
331 336 'mention': True,
332 337 },
333 338
334 339 'cs_comment+file': {
335 340 'user': user,
336 341 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
337 342 'status_change': None,
338 343 'status_change_type': None,
339 344
340 345 'commit_target_repo_url': 'http://foo.example.com/#comment1',
341 346 'repo_name': 'test-repo',
342 347
343 348 'comment_type': 'note',
344 349 'comment_file': 'test-file.py',
345 350 'comment_line': 'n100',
346 351
347 352 'commit_comment_url': 'http://comment-url',
348 353 'commit_comment_reply_url': 'http://comment-url#reply',
349 354 'comment_body': 'This is my comment body. *I like !*',
350 355 'comment_id': 2048,
351 356 'renderer_type': 'markdown',
352 357 'mention': True,
353 358 },
354 359
355 360 'pull_request': {
356 361 'user': user,
357 362 'pull_request': pr,
358 363 'pull_request_commits': [
359 364 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
360 365 my-account: moved email closer to profile as it's similar data just moved outside.
361 366 '''),
362 367 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
363 368 users: description edit fixes
364 369
365 370 - tests
366 371 - added metatags info
367 372 '''),
368 373 ],
369 374
370 375 'pull_request_target_repo': target_repo,
371 376 'pull_request_target_repo_url': 'http://target-repo/url',
372 377
373 378 'pull_request_source_repo': source_repo,
374 379 'pull_request_source_repo_url': 'http://source-repo/url',
375 380
376 381 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
377 382 'user_role': 'reviewer',
378 383 },
379 384
380 385 'pull_request+reviewer_role': {
381 386 'user': user,
382 387 'pull_request': pr,
383 388 'pull_request_commits': [
384 389 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
385 390 my-account: moved email closer to profile as it's similar data just moved outside.
386 391 '''),
387 392 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
388 393 users: description edit fixes
389 394
390 395 - tests
391 396 - added metatags info
392 397 '''),
393 398 ],
394 399
395 400 'pull_request_target_repo': target_repo,
396 401 'pull_request_target_repo_url': 'http://target-repo/url',
397 402
398 403 'pull_request_source_repo': source_repo,
399 404 'pull_request_source_repo_url': 'http://source-repo/url',
400 405
401 406 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
402 407 'user_role': 'reviewer',
403 408 },
404 409
405 410 'pull_request+observer_role': {
406 411 'user': user,
407 412 'pull_request': pr,
408 413 'pull_request_commits': [
409 414 ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
410 415 my-account: moved email closer to profile as it's similar data just moved outside.
411 416 '''),
412 417 ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
413 418 users: description edit fixes
414 419
415 420 - tests
416 421 - added metatags info
417 422 '''),
418 423 ],
419 424
420 425 'pull_request_target_repo': target_repo,
421 426 'pull_request_target_repo_url': 'http://target-repo/url',
422 427
423 428 'pull_request_source_repo': source_repo,
424 429 'pull_request_source_repo_url': 'http://source-repo/url',
425 430
426 431 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
427 432 'user_role': 'observer'
428 433 }
429 434 }
430 435
431 436 template_type = email_id.split('+')[0]
432 437 (c.subject, c.email_body, c.email_body_plaintext) = EmailNotificationModel().render_email(
433 438 template_type, **email_kwargs.get(email_id, {}))
434 439
435 440 test_email = self.request.GET.get('email')
436 441 if test_email:
437 442 recipients = [test_email]
438 443 run_task(tasks.send_email, recipients, c.subject,
439 444 c.email_body_plaintext, c.email_body)
440 445
441 446 if self.request.matched_route.name == 'debug_style_email_plain_rendered':
442 447 template = 'debug_style/email_plain_rendered.mako'
443 448 else:
444 449 template = 'debug_style/email.mako'
445 450 return render_to_response(
446 451 template, self._get_template_context(c),
447 452 request=self.request)
448 453
449 454 def template(self):
450 455 t_path = self.request.matchdict['t_path']
451 456 c = self.load_default_context()
452 457 c.active = os.path.splitext(t_path)[0]
453 458 c.came_from = ''
454 459 # NOTE(marcink): extend the email types with variations based on data sets
455 460 c.email_types = {
456 461 'cs_comment+file': {},
457 462 'cs_comment+status': {},
458 463
459 464 'pull_request_comment+file': {},
460 465 'pull_request_comment+status': {},
461 466
462 467 'pull_request_update': {},
463 468
464 469 'pull_request+reviewer_role': {},
465 470 'pull_request+observer_role': {},
466 471 }
467 472 c.email_types.update(EmailNotificationModel.email_types)
468 473
469 474 return render_to_response(
470 475 'debug_style/' + t_path, self._get_template_context(c),
471 476 request=self.request)
@@ -1,1658 +1,1679 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import mock
21 21 import pytest
22 22
23 23 import rhodecode
24 24 from rhodecode.lib.vcs.backends.base import MergeResponse, MergeFailureReason
25 25 from rhodecode.lib.vcs.nodes import FileNode
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.model.changeset_status import ChangesetStatusModel
28 28 from rhodecode.model.db import (
29 29 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment, Repository)
30 30 from rhodecode.model.meta import Session
31 31 from rhodecode.model.pull_request import PullRequestModel
32 32 from rhodecode.model.user import UserModel
33 33 from rhodecode.model.comment import CommentsModel
34 34 from rhodecode.tests import (
35 35 assert_session_flash, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 36
37 37
38 38 def route_path(name, params=None, **kwargs):
39 39 import urllib
40 40
41 41 base_url = {
42 42 'repo_changelog': '/{repo_name}/changelog',
43 43 'repo_changelog_file': '/{repo_name}/changelog/{commit_id}/{f_path}',
44 44 'repo_commits': '/{repo_name}/commits',
45 45 'repo_commits_file': '/{repo_name}/commits/{commit_id}/{f_path}',
46 46 'pullrequest_show': '/{repo_name}/pull-request/{pull_request_id}',
47 47 'pullrequest_show_all': '/{repo_name}/pull-request',
48 48 'pullrequest_show_all_data': '/{repo_name}/pull-request-data',
49 49 'pullrequest_repo_refs': '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
50 50 'pullrequest_repo_targets': '/{repo_name}/pull-request/repo-destinations',
51 51 'pullrequest_new': '/{repo_name}/pull-request/new',
52 52 'pullrequest_create': '/{repo_name}/pull-request/create',
53 53 'pullrequest_update': '/{repo_name}/pull-request/{pull_request_id}/update',
54 54 'pullrequest_merge': '/{repo_name}/pull-request/{pull_request_id}/merge',
55 55 'pullrequest_delete': '/{repo_name}/pull-request/{pull_request_id}/delete',
56 56 'pullrequest_comment_create': '/{repo_name}/pull-request/{pull_request_id}/comment',
57 57 'pullrequest_comment_delete': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/delete',
58 58 'pullrequest_comment_edit': '/{repo_name}/pull-request/{pull_request_id}/comment/{comment_id}/edit',
59 59 }[name].format(**kwargs)
60 60
61 61 if params:
62 62 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
63 63 return base_url
64 64
65 65
66 66 @pytest.mark.usefixtures('app', 'autologin_user')
67 67 @pytest.mark.backends("git", "hg")
68 68 class TestPullrequestsView(object):
69 69
70 70 def test_index(self, backend):
71 71 self.app.get(route_path(
72 72 'pullrequest_new',
73 73 repo_name=backend.repo_name))
74 74
75 75 def test_option_menu_create_pull_request_exists(self, backend):
76 76 repo_name = backend.repo_name
77 77 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
78 78
79 79 create_pr_link = '<a href="%s">Create Pull Request</a>' % route_path(
80 80 'pullrequest_new', repo_name=repo_name)
81 81 response.mustcontain(create_pr_link)
82 82
83 83 def test_create_pr_form_with_raw_commit_id(self, backend):
84 84 repo = backend.repo
85 85
86 86 self.app.get(
87 87 route_path('pullrequest_new', repo_name=repo.repo_name,
88 88 commit=repo.get_commit().raw_id),
89 89 status=200)
90 90
91 91 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
92 92 @pytest.mark.parametrize('range_diff', ["0", "1"])
93 93 def test_show(self, pr_util, pr_merge_enabled, range_diff):
94 94 pull_request = pr_util.create_pull_request(
95 95 mergeable=pr_merge_enabled, enable_notifications=False)
96 96
97 97 response = self.app.get(route_path(
98 98 'pullrequest_show',
99 99 repo_name=pull_request.target_repo.scm_instance().name,
100 100 pull_request_id=pull_request.pull_request_id,
101 101 params={'range-diff': range_diff}))
102 102
103 103 for commit_id in pull_request.revisions:
104 104 response.mustcontain(commit_id)
105 105
106 106 response.mustcontain(pull_request.target_ref_parts.type)
107 107 response.mustcontain(pull_request.target_ref_parts.name)
108 108
109 109 response.mustcontain('class="pull-request-merge"')
110 110
111 111 if pr_merge_enabled:
112 112 response.mustcontain('Pull request reviewer approval is pending')
113 113 else:
114 114 response.mustcontain('Server-side pull request merging is disabled.')
115 115
116 116 if range_diff == "1":
117 117 response.mustcontain('Turn off: Show the diff as commit range')
118 118
119 119 def test_show_versions_of_pr(self, backend, csrf_token):
120 120 commits = [
121 121 {'message': 'initial-commit',
122 122 'added': [FileNode('test-file.txt', 'LINE1\n')]},
123 123
124 124 {'message': 'commit-1',
125 125 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\n')]},
126 126 # Above is the initial version of PR that changes a single line
127 127
128 128 # from now on we'll add 3x commit adding a nother line on each step
129 129 {'message': 'commit-2',
130 130 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\n')]},
131 131
132 132 {'message': 'commit-3',
133 133 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\n')]},
134 134
135 135 {'message': 'commit-4',
136 136 'changed': [FileNode('test-file.txt', 'LINE1\nLINE2\nLINE3\nLINE4\nLINE5\n')]},
137 137 ]
138 138
139 139 commit_ids = backend.create_master_repo(commits)
140 140 target = backend.create_repo(heads=['initial-commit'])
141 141 source = backend.create_repo(heads=['commit-1'])
142 142 source_repo_name = source.repo_name
143 143 target_repo_name = target.repo_name
144 144
145 145 target_ref = 'branch:{branch}:{commit_id}'.format(
146 146 branch=backend.default_branch_name, commit_id=commit_ids['initial-commit'])
147 147 source_ref = 'branch:{branch}:{commit_id}'.format(
148 148 branch=backend.default_branch_name, commit_id=commit_ids['commit-1'])
149 149
150 150 response = self.app.post(
151 151 route_path('pullrequest_create', repo_name=source.repo_name),
152 152 [
153 153 ('source_repo', source_repo_name),
154 154 ('source_ref', source_ref),
155 155 ('target_repo', target_repo_name),
156 156 ('target_ref', target_ref),
157 157 ('common_ancestor', commit_ids['initial-commit']),
158 158 ('pullrequest_title', 'Title'),
159 159 ('pullrequest_desc', 'Description'),
160 160 ('description_renderer', 'markdown'),
161 161 ('__start__', 'review_members:sequence'),
162 162 ('__start__', 'reviewer:mapping'),
163 163 ('user_id', '1'),
164 164 ('__start__', 'reasons:sequence'),
165 165 ('reason', 'Some reason'),
166 166 ('__end__', 'reasons:sequence'),
167 167 ('__start__', 'rules:sequence'),
168 168 ('__end__', 'rules:sequence'),
169 169 ('mandatory', 'False'),
170 170 ('__end__', 'reviewer:mapping'),
171 171 ('__end__', 'review_members:sequence'),
172 172 ('__start__', 'revisions:sequence'),
173 173 ('revisions', commit_ids['commit-1']),
174 174 ('__end__', 'revisions:sequence'),
175 175 ('user', ''),
176 176 ('csrf_token', csrf_token),
177 177 ],
178 178 status=302)
179 179
180 180 location = response.headers['Location']
181 181
182 182 pull_request_id = location.rsplit('/', 1)[1]
183 183 assert pull_request_id != 'new'
184 184 pull_request = PullRequest.get(int(pull_request_id))
185 185
186 186 pull_request_id = pull_request.pull_request_id
187 187
188 188 # Show initial version of PR
189 189 response = self.app.get(
190 190 route_path('pullrequest_show',
191 191 repo_name=target_repo_name,
192 192 pull_request_id=pull_request_id))
193 193
194 194 response.mustcontain('commit-1')
195 195 response.mustcontain(no=['commit-2'])
196 196 response.mustcontain(no=['commit-3'])
197 197 response.mustcontain(no=['commit-4'])
198 198
199 199 response.mustcontain('cb-addition"></span><span>LINE2</span>')
200 200 response.mustcontain(no=['LINE3'])
201 201 response.mustcontain(no=['LINE4'])
202 202 response.mustcontain(no=['LINE5'])
203 203
204 204 # update PR #1
205 205 source_repo = Repository.get_by_repo_name(source_repo_name)
206 206 backend.pull_heads(source_repo, heads=['commit-2'])
207 207 response = self.app.post(
208 208 route_path('pullrequest_update',
209 209 repo_name=target_repo_name, pull_request_id=pull_request_id),
210 210 params={'update_commits': 'true', 'csrf_token': csrf_token})
211 211
212 212 # update PR #2
213 213 source_repo = Repository.get_by_repo_name(source_repo_name)
214 214 backend.pull_heads(source_repo, heads=['commit-3'])
215 215 response = self.app.post(
216 216 route_path('pullrequest_update',
217 217 repo_name=target_repo_name, pull_request_id=pull_request_id),
218 218 params={'update_commits': 'true', 'csrf_token': csrf_token})
219 219
220 220 # update PR #3
221 221 source_repo = Repository.get_by_repo_name(source_repo_name)
222 222 backend.pull_heads(source_repo, heads=['commit-4'])
223 223 response = self.app.post(
224 224 route_path('pullrequest_update',
225 225 repo_name=target_repo_name, pull_request_id=pull_request_id),
226 226 params={'update_commits': 'true', 'csrf_token': csrf_token})
227 227
228 228 # Show final version !
229 229 response = self.app.get(
230 230 route_path('pullrequest_show',
231 231 repo_name=target_repo_name,
232 232 pull_request_id=pull_request_id))
233 233
234 234 # 3 updates, and the latest == 4
235 235 response.mustcontain('4 versions available for this pull request')
236 236 response.mustcontain(no=['rhodecode diff rendering error'])
237 237
238 238 # initial show must have 3 commits, and 3 adds
239 239 response.mustcontain('commit-1')
240 240 response.mustcontain('commit-2')
241 241 response.mustcontain('commit-3')
242 242 response.mustcontain('commit-4')
243 243
244 244 response.mustcontain('cb-addition"></span><span>LINE2</span>')
245 245 response.mustcontain('cb-addition"></span><span>LINE3</span>')
246 246 response.mustcontain('cb-addition"></span><span>LINE4</span>')
247 247 response.mustcontain('cb-addition"></span><span>LINE5</span>')
248 248
249 249 # fetch versions
250 250 pr = PullRequest.get(pull_request_id)
251 251 versions = [x.pull_request_version_id for x in pr.versions.all()]
252 252 assert len(versions) == 3
253 253
254 254 # show v1,v2,v3,v4
255 255 def cb_line(text):
256 256 return 'cb-addition"></span><span>{}</span>'.format(text)
257 257
258 258 def cb_context(text):
259 259 return '<span class="cb-code"><span class="cb-action cb-context">' \
260 260 '</span><span>{}</span></span>'.format(text)
261 261
262 262 commit_tests = {
263 263 # in response, not in response
264 264 1: (['commit-1'], ['commit-2', 'commit-3', 'commit-4']),
265 265 2: (['commit-1', 'commit-2'], ['commit-3', 'commit-4']),
266 266 3: (['commit-1', 'commit-2', 'commit-3'], ['commit-4']),
267 267 4: (['commit-1', 'commit-2', 'commit-3', 'commit-4'], []),
268 268 }
269 269 diff_tests = {
270 270 1: (['LINE2'], ['LINE3', 'LINE4', 'LINE5']),
271 271 2: (['LINE2', 'LINE3'], ['LINE4', 'LINE5']),
272 272 3: (['LINE2', 'LINE3', 'LINE4'], ['LINE5']),
273 273 4: (['LINE2', 'LINE3', 'LINE4', 'LINE5'], []),
274 274 }
275 275 for idx, ver in enumerate(versions, 1):
276 276
277 277 response = self.app.get(
278 278 route_path('pullrequest_show',
279 279 repo_name=target_repo_name,
280 280 pull_request_id=pull_request_id,
281 281 params={'version': ver}))
282 282
283 283 response.mustcontain(no=['rhodecode diff rendering error'])
284 284 response.mustcontain('Showing changes at v{}'.format(idx))
285 285
286 286 yes, no = commit_tests[idx]
287 287 for y in yes:
288 288 response.mustcontain(y)
289 289 for n in no:
290 290 response.mustcontain(no=n)
291 291
292 292 yes, no = diff_tests[idx]
293 293 for y in yes:
294 294 response.mustcontain(cb_line(y))
295 295 for n in no:
296 296 response.mustcontain(no=n)
297 297
298 298 # show diff between versions
299 299 diff_compare_tests = {
300 300 1: (['LINE3'], ['LINE1', 'LINE2']),
301 301 2: (['LINE3', 'LINE4'], ['LINE1', 'LINE2']),
302 302 3: (['LINE3', 'LINE4', 'LINE5'], ['LINE1', 'LINE2']),
303 303 }
304 304 for idx, ver in enumerate(versions, 1):
305 305 adds, context = diff_compare_tests[idx]
306 306
307 307 to_ver = ver+1
308 308 if idx == 3:
309 309 to_ver = 'latest'
310 310
311 311 response = self.app.get(
312 312 route_path('pullrequest_show',
313 313 repo_name=target_repo_name,
314 314 pull_request_id=pull_request_id,
315 315 params={'from_version': versions[0], 'version': to_ver}))
316 316
317 317 response.mustcontain(no=['rhodecode diff rendering error'])
318 318
319 319 for a in adds:
320 320 response.mustcontain(cb_line(a))
321 321 for c in context:
322 322 response.mustcontain(cb_context(c))
323 323
324 324 # test version v2 -> v3
325 325 response = self.app.get(
326 326 route_path('pullrequest_show',
327 327 repo_name=target_repo_name,
328 328 pull_request_id=pull_request_id,
329 329 params={'from_version': versions[1], 'version': versions[2]}))
330 330
331 331 response.mustcontain(cb_context('LINE1'))
332 332 response.mustcontain(cb_context('LINE2'))
333 333 response.mustcontain(cb_context('LINE3'))
334 334 response.mustcontain(cb_line('LINE4'))
335 335
336 336 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
337 337 # Logout
338 338 response = self.app.post(
339 339 h.route_path('logout'),
340 340 params={'csrf_token': csrf_token})
341 341 # Login as regular user
342 342 response = self.app.post(h.route_path('login'),
343 343 {'username': TEST_USER_REGULAR_LOGIN,
344 344 'password': 'test12'})
345 345
346 346 pull_request = pr_util.create_pull_request(
347 347 author=TEST_USER_REGULAR_LOGIN)
348 348
349 349 response = self.app.get(route_path(
350 350 'pullrequest_show',
351 351 repo_name=pull_request.target_repo.scm_instance().name,
352 352 pull_request_id=pull_request.pull_request_id))
353 353
354 354 response.mustcontain('Server-side pull request merging is disabled.')
355 355
356 356 assert_response = response.assert_response()
357 357 # for regular user without a merge permissions, we don't see it
358 358 assert_response.no_element_exists('#close-pull-request-action')
359 359
360 360 user_util.grant_user_permission_to_repo(
361 361 pull_request.target_repo,
362 362 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
363 363 'repository.write')
364 364 response = self.app.get(route_path(
365 365 'pullrequest_show',
366 366 repo_name=pull_request.target_repo.scm_instance().name,
367 367 pull_request_id=pull_request.pull_request_id))
368 368
369 369 response.mustcontain('Server-side pull request merging is disabled.')
370 370
371 371 assert_response = response.assert_response()
372 372 # now regular user has a merge permissions, we have CLOSE button
373 373 assert_response.one_element_exists('#close-pull-request-action')
374 374
375 375 def test_show_invalid_commit_id(self, pr_util):
376 376 # Simulating invalid revisions which will cause a lookup error
377 377 pull_request = pr_util.create_pull_request()
378 378 pull_request.revisions = ['invalid']
379 379 Session().add(pull_request)
380 380 Session().commit()
381 381
382 382 response = self.app.get(route_path(
383 383 'pullrequest_show',
384 384 repo_name=pull_request.target_repo.scm_instance().name,
385 385 pull_request_id=pull_request.pull_request_id))
386 386
387 387 for commit_id in pull_request.revisions:
388 388 response.mustcontain(commit_id)
389 389
390 390 def test_show_invalid_source_reference(self, pr_util):
391 391 pull_request = pr_util.create_pull_request()
392 392 pull_request.source_ref = 'branch:b:invalid'
393 393 Session().add(pull_request)
394 394 Session().commit()
395 395
396 396 self.app.get(route_path(
397 397 'pullrequest_show',
398 398 repo_name=pull_request.target_repo.scm_instance().name,
399 399 pull_request_id=pull_request.pull_request_id))
400 400
401 401 def test_edit_title_description(self, pr_util, csrf_token):
402 402 pull_request = pr_util.create_pull_request()
403 403 pull_request_id = pull_request.pull_request_id
404 404
405 405 response = self.app.post(
406 406 route_path('pullrequest_update',
407 407 repo_name=pull_request.target_repo.repo_name,
408 408 pull_request_id=pull_request_id),
409 409 params={
410 410 'edit_pull_request': 'true',
411 411 'title': 'New title',
412 412 'description': 'New description',
413 413 'csrf_token': csrf_token})
414 414
415 415 assert_session_flash(
416 416 response, u'Pull request title & description updated.',
417 417 category='success')
418 418
419 419 pull_request = PullRequest.get(pull_request_id)
420 420 assert pull_request.title == 'New title'
421 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 444 def test_edit_title_description_closed(self, pr_util, csrf_token):
424 445 pull_request = pr_util.create_pull_request()
425 446 pull_request_id = pull_request.pull_request_id
426 447 repo_name = pull_request.target_repo.repo_name
427 448 pr_util.close()
428 449
429 450 response = self.app.post(
430 451 route_path('pullrequest_update',
431 452 repo_name=repo_name, pull_request_id=pull_request_id),
432 453 params={
433 454 'edit_pull_request': 'true',
434 455 'title': 'New title',
435 456 'description': 'New description',
436 457 'csrf_token': csrf_token}, status=200)
437 458 assert_session_flash(
438 459 response, u'Cannot update closed pull requests.',
439 460 category='error')
440 461
441 462 def test_update_invalid_source_reference(self, pr_util, csrf_token):
442 463 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
443 464
444 465 pull_request = pr_util.create_pull_request()
445 466 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
446 467 Session().add(pull_request)
447 468 Session().commit()
448 469
449 470 pull_request_id = pull_request.pull_request_id
450 471
451 472 response = self.app.post(
452 473 route_path('pullrequest_update',
453 474 repo_name=pull_request.target_repo.repo_name,
454 475 pull_request_id=pull_request_id),
455 476 params={'update_commits': 'true', 'csrf_token': csrf_token})
456 477
457 478 expected_msg = str(PullRequestModel.UPDATE_STATUS_MESSAGES[
458 479 UpdateFailureReason.MISSING_SOURCE_REF])
459 480 assert_session_flash(response, expected_msg, category='error')
460 481
461 482 def test_missing_target_reference(self, pr_util, csrf_token):
462 483 from rhodecode.lib.vcs.backends.base import MergeFailureReason
463 484 pull_request = pr_util.create_pull_request(
464 485 approved=True, mergeable=True)
465 486 unicode_reference = u'branch:invalid-branch:invalid-commit-id'
466 487 pull_request.target_ref = unicode_reference
467 488 Session().add(pull_request)
468 489 Session().commit()
469 490
470 491 pull_request_id = pull_request.pull_request_id
471 492 pull_request_url = route_path(
472 493 'pullrequest_show',
473 494 repo_name=pull_request.target_repo.repo_name,
474 495 pull_request_id=pull_request_id)
475 496
476 497 response = self.app.get(pull_request_url)
477 498 target_ref_id = 'invalid-branch'
478 499 merge_resp = MergeResponse(
479 500 True, True, '', MergeFailureReason.MISSING_TARGET_REF,
480 501 metadata={'target_ref': PullRequest.unicode_to_reference(unicode_reference)})
481 502 response.assert_response().element_contains(
482 503 'div[data-role="merge-message"]', merge_resp.merge_status_message)
483 504
484 505 def test_comment_and_close_pull_request_custom_message_approved(
485 506 self, pr_util, csrf_token, xhr_header):
486 507
487 508 pull_request = pr_util.create_pull_request(approved=True)
488 509 pull_request_id = pull_request.pull_request_id
489 510 author = pull_request.user_id
490 511 repo = pull_request.target_repo.repo_id
491 512
492 513 self.app.post(
493 514 route_path('pullrequest_comment_create',
494 515 repo_name=pull_request.target_repo.scm_instance().name,
495 516 pull_request_id=pull_request_id),
496 517 params={
497 518 'close_pull_request': '1',
498 519 'text': 'Closing a PR',
499 520 'csrf_token': csrf_token},
500 521 extra_environ=xhr_header,)
501 522
502 523 journal = UserLog.query()\
503 524 .filter(UserLog.user_id == author)\
504 525 .filter(UserLog.repository_id == repo) \
505 526 .order_by(UserLog.user_log_id.asc()) \
506 527 .all()
507 528 assert journal[-1].action == 'repo.pull_request.close'
508 529
509 530 pull_request = PullRequest.get(pull_request_id)
510 531 assert pull_request.is_closed()
511 532
512 533 status = ChangesetStatusModel().get_status(
513 534 pull_request.source_repo, pull_request=pull_request)
514 535 assert status == ChangesetStatus.STATUS_APPROVED
515 536 comments = ChangesetComment().query() \
516 537 .filter(ChangesetComment.pull_request == pull_request) \
517 538 .order_by(ChangesetComment.comment_id.asc())\
518 539 .all()
519 540 assert comments[-1].text == 'Closing a PR'
520 541
521 542 def test_comment_force_close_pull_request_rejected(
522 543 self, pr_util, csrf_token, xhr_header):
523 544 pull_request = pr_util.create_pull_request()
524 545 pull_request_id = pull_request.pull_request_id
525 546 PullRequestModel().update_reviewers(
526 547 pull_request_id, [
527 548 (1, ['reason'], False, 'reviewer', []),
528 549 (2, ['reason2'], False, 'reviewer', [])],
529 550 pull_request.author)
530 551 author = pull_request.user_id
531 552 repo = pull_request.target_repo.repo_id
532 553
533 554 self.app.post(
534 555 route_path('pullrequest_comment_create',
535 556 repo_name=pull_request.target_repo.scm_instance().name,
536 557 pull_request_id=pull_request_id),
537 558 params={
538 559 'close_pull_request': '1',
539 560 'csrf_token': csrf_token},
540 561 extra_environ=xhr_header)
541 562
542 563 pull_request = PullRequest.get(pull_request_id)
543 564
544 565 journal = UserLog.query()\
545 566 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
546 567 .order_by(UserLog.user_log_id.asc()) \
547 568 .all()
548 569 assert journal[-1].action == 'repo.pull_request.close'
549 570
550 571 # check only the latest status, not the review status
551 572 status = ChangesetStatusModel().get_status(
552 573 pull_request.source_repo, pull_request=pull_request)
553 574 assert status == ChangesetStatus.STATUS_REJECTED
554 575
555 576 def test_comment_and_close_pull_request(
556 577 self, pr_util, csrf_token, xhr_header):
557 578 pull_request = pr_util.create_pull_request()
558 579 pull_request_id = pull_request.pull_request_id
559 580
560 581 response = self.app.post(
561 582 route_path('pullrequest_comment_create',
562 583 repo_name=pull_request.target_repo.scm_instance().name,
563 584 pull_request_id=pull_request.pull_request_id),
564 585 params={
565 586 'close_pull_request': 'true',
566 587 'csrf_token': csrf_token},
567 588 extra_environ=xhr_header)
568 589
569 590 assert response.json
570 591
571 592 pull_request = PullRequest.get(pull_request_id)
572 593 assert pull_request.is_closed()
573 594
574 595 # check only the latest status, not the review status
575 596 status = ChangesetStatusModel().get_status(
576 597 pull_request.source_repo, pull_request=pull_request)
577 598 assert status == ChangesetStatus.STATUS_REJECTED
578 599
579 600 def test_comment_and_close_pull_request_try_edit_comment(
580 601 self, pr_util, csrf_token, xhr_header
581 602 ):
582 603 pull_request = pr_util.create_pull_request()
583 604 pull_request_id = pull_request.pull_request_id
584 605 target_scm = pull_request.target_repo.scm_instance()
585 606 target_scm_name = target_scm.name
586 607
587 608 response = self.app.post(
588 609 route_path(
589 610 'pullrequest_comment_create',
590 611 repo_name=target_scm_name,
591 612 pull_request_id=pull_request_id,
592 613 ),
593 614 params={
594 615 'close_pull_request': 'true',
595 616 'csrf_token': csrf_token,
596 617 },
597 618 extra_environ=xhr_header)
598 619
599 620 assert response.json
600 621
601 622 pull_request = PullRequest.get(pull_request_id)
602 623 target_scm = pull_request.target_repo.scm_instance()
603 624 target_scm_name = target_scm.name
604 625 assert pull_request.is_closed()
605 626
606 627 # check only the latest status, not the review status
607 628 status = ChangesetStatusModel().get_status(
608 629 pull_request.source_repo, pull_request=pull_request)
609 630 assert status == ChangesetStatus.STATUS_REJECTED
610 631
611 632 for comment_id in response.json.keys():
612 633 test_text = 'test'
613 634 response = self.app.post(
614 635 route_path(
615 636 'pullrequest_comment_edit',
616 637 repo_name=target_scm_name,
617 638 pull_request_id=pull_request_id,
618 639 comment_id=comment_id,
619 640 ),
620 641 extra_environ=xhr_header,
621 642 params={
622 643 'csrf_token': csrf_token,
623 644 'text': test_text,
624 645 },
625 646 status=403,
626 647 )
627 648 assert response.status_int == 403
628 649
629 650 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
630 651 pull_request = pr_util.create_pull_request()
631 652 target_scm = pull_request.target_repo.scm_instance()
632 653 target_scm_name = target_scm.name
633 654
634 655 response = self.app.post(
635 656 route_path(
636 657 'pullrequest_comment_create',
637 658 repo_name=target_scm_name,
638 659 pull_request_id=pull_request.pull_request_id),
639 660 params={
640 661 'csrf_token': csrf_token,
641 662 'text': 'init',
642 663 },
643 664 extra_environ=xhr_header,
644 665 )
645 666 assert response.json
646 667
647 668 for comment_id in response.json.keys():
648 669 assert comment_id
649 670 test_text = 'test'
650 671 self.app.post(
651 672 route_path(
652 673 'pullrequest_comment_edit',
653 674 repo_name=target_scm_name,
654 675 pull_request_id=pull_request.pull_request_id,
655 676 comment_id=comment_id,
656 677 ),
657 678 extra_environ=xhr_header,
658 679 params={
659 680 'csrf_token': csrf_token,
660 681 'text': test_text,
661 682 'version': '0',
662 683 },
663 684
664 685 )
665 686 text_form_db = ChangesetComment.query().filter(
666 687 ChangesetComment.comment_id == comment_id).first().text
667 688 assert test_text == text_form_db
668 689
669 690 def test_comment_and_comment_edit(self, pr_util, csrf_token, xhr_header):
670 691 pull_request = pr_util.create_pull_request()
671 692 target_scm = pull_request.target_repo.scm_instance()
672 693 target_scm_name = target_scm.name
673 694
674 695 response = self.app.post(
675 696 route_path(
676 697 'pullrequest_comment_create',
677 698 repo_name=target_scm_name,
678 699 pull_request_id=pull_request.pull_request_id),
679 700 params={
680 701 'csrf_token': csrf_token,
681 702 'text': 'init',
682 703 },
683 704 extra_environ=xhr_header,
684 705 )
685 706 assert response.json
686 707
687 708 for comment_id in response.json.keys():
688 709 test_text = 'init'
689 710 response = self.app.post(
690 711 route_path(
691 712 'pullrequest_comment_edit',
692 713 repo_name=target_scm_name,
693 714 pull_request_id=pull_request.pull_request_id,
694 715 comment_id=comment_id,
695 716 ),
696 717 extra_environ=xhr_header,
697 718 params={
698 719 'csrf_token': csrf_token,
699 720 'text': test_text,
700 721 'version': '0',
701 722 },
702 723 status=404,
703 724
704 725 )
705 726 assert response.status_int == 404
706 727
707 728 def test_comment_and_try_edit_already_edited(self, pr_util, csrf_token, xhr_header):
708 729 pull_request = pr_util.create_pull_request()
709 730 target_scm = pull_request.target_repo.scm_instance()
710 731 target_scm_name = target_scm.name
711 732
712 733 response = self.app.post(
713 734 route_path(
714 735 'pullrequest_comment_create',
715 736 repo_name=target_scm_name,
716 737 pull_request_id=pull_request.pull_request_id),
717 738 params={
718 739 'csrf_token': csrf_token,
719 740 'text': 'init',
720 741 },
721 742 extra_environ=xhr_header,
722 743 )
723 744 assert response.json
724 745 for comment_id in response.json.keys():
725 746 test_text = 'test'
726 747 self.app.post(
727 748 route_path(
728 749 'pullrequest_comment_edit',
729 750 repo_name=target_scm_name,
730 751 pull_request_id=pull_request.pull_request_id,
731 752 comment_id=comment_id,
732 753 ),
733 754 extra_environ=xhr_header,
734 755 params={
735 756 'csrf_token': csrf_token,
736 757 'text': test_text,
737 758 'version': '0',
738 759 },
739 760
740 761 )
741 762 test_text_v2 = 'test_v2'
742 763 response = self.app.post(
743 764 route_path(
744 765 'pullrequest_comment_edit',
745 766 repo_name=target_scm_name,
746 767 pull_request_id=pull_request.pull_request_id,
747 768 comment_id=comment_id,
748 769 ),
749 770 extra_environ=xhr_header,
750 771 params={
751 772 'csrf_token': csrf_token,
752 773 'text': test_text_v2,
753 774 'version': '0',
754 775 },
755 776 status=409,
756 777 )
757 778 assert response.status_int == 409
758 779
759 780 text_form_db = ChangesetComment.query().filter(
760 781 ChangesetComment.comment_id == comment_id).first().text
761 782
762 783 assert test_text == text_form_db
763 784 assert test_text_v2 != text_form_db
764 785
765 786 def test_comment_and_comment_edit_permissions_forbidden(
766 787 self, autologin_regular_user, user_regular, user_admin, pr_util,
767 788 csrf_token, xhr_header):
768 789 pull_request = pr_util.create_pull_request(
769 790 author=user_admin.username, enable_notifications=False)
770 791 comment = CommentsModel().create(
771 792 text='test',
772 793 repo=pull_request.target_repo.scm_instance().name,
773 794 user=user_admin,
774 795 pull_request=pull_request,
775 796 )
776 797 response = self.app.post(
777 798 route_path(
778 799 'pullrequest_comment_edit',
779 800 repo_name=pull_request.target_repo.scm_instance().name,
780 801 pull_request_id=pull_request.pull_request_id,
781 802 comment_id=comment.comment_id,
782 803 ),
783 804 extra_environ=xhr_header,
784 805 params={
785 806 'csrf_token': csrf_token,
786 807 'text': 'test_text',
787 808 },
788 809 status=403,
789 810 )
790 811 assert response.status_int == 403
791 812
792 813 def test_create_pull_request(self, backend, csrf_token):
793 814 commits = [
794 815 {'message': 'ancestor'},
795 816 {'message': 'change'},
796 817 {'message': 'change2'},
797 818 ]
798 819 commit_ids = backend.create_master_repo(commits)
799 820 target = backend.create_repo(heads=['ancestor'])
800 821 source = backend.create_repo(heads=['change2'])
801 822
802 823 response = self.app.post(
803 824 route_path('pullrequest_create', repo_name=source.repo_name),
804 825 [
805 826 ('source_repo', source.repo_name),
806 827 ('source_ref', 'branch:default:' + commit_ids['change2']),
807 828 ('target_repo', target.repo_name),
808 829 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
809 830 ('common_ancestor', commit_ids['ancestor']),
810 831 ('pullrequest_title', 'Title'),
811 832 ('pullrequest_desc', 'Description'),
812 833 ('description_renderer', 'markdown'),
813 834 ('__start__', 'review_members:sequence'),
814 835 ('__start__', 'reviewer:mapping'),
815 836 ('user_id', '1'),
816 837 ('__start__', 'reasons:sequence'),
817 838 ('reason', 'Some reason'),
818 839 ('__end__', 'reasons:sequence'),
819 840 ('__start__', 'rules:sequence'),
820 841 ('__end__', 'rules:sequence'),
821 842 ('mandatory', 'False'),
822 843 ('__end__', 'reviewer:mapping'),
823 844 ('__end__', 'review_members:sequence'),
824 845 ('__start__', 'revisions:sequence'),
825 846 ('revisions', commit_ids['change']),
826 847 ('revisions', commit_ids['change2']),
827 848 ('__end__', 'revisions:sequence'),
828 849 ('user', ''),
829 850 ('csrf_token', csrf_token),
830 851 ],
831 852 status=302)
832 853
833 854 location = response.headers['Location']
834 855 pull_request_id = location.rsplit('/', 1)[1]
835 856 assert pull_request_id != 'new'
836 857 pull_request = PullRequest.get(int(pull_request_id))
837 858
838 859 # check that we have now both revisions
839 860 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
840 861 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
841 862 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
842 863 assert pull_request.target_ref == expected_target_ref
843 864
844 865 def test_reviewer_notifications(self, backend, csrf_token):
845 866 # We have to use the app.post for this test so it will create the
846 867 # notifications properly with the new PR
847 868 commits = [
848 869 {'message': 'ancestor',
849 870 'added': [FileNode('file_A', content='content_of_ancestor')]},
850 871 {'message': 'change',
851 872 'added': [FileNode('file_a', content='content_of_change')]},
852 873 {'message': 'change-child'},
853 874 {'message': 'ancestor-child', 'parents': ['ancestor'],
854 875 'added': [
855 876 FileNode('file_B', content='content_of_ancestor_child')]},
856 877 {'message': 'ancestor-child-2'},
857 878 ]
858 879 commit_ids = backend.create_master_repo(commits)
859 880 target = backend.create_repo(heads=['ancestor-child'])
860 881 source = backend.create_repo(heads=['change'])
861 882
862 883 response = self.app.post(
863 884 route_path('pullrequest_create', repo_name=source.repo_name),
864 885 [
865 886 ('source_repo', source.repo_name),
866 887 ('source_ref', 'branch:default:' + commit_ids['change']),
867 888 ('target_repo', target.repo_name),
868 889 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
869 890 ('common_ancestor', commit_ids['ancestor']),
870 891 ('pullrequest_title', 'Title'),
871 892 ('pullrequest_desc', 'Description'),
872 893 ('description_renderer', 'markdown'),
873 894 ('__start__', 'review_members:sequence'),
874 895 ('__start__', 'reviewer:mapping'),
875 896 ('user_id', '2'),
876 897 ('__start__', 'reasons:sequence'),
877 898 ('reason', 'Some reason'),
878 899 ('__end__', 'reasons:sequence'),
879 900 ('__start__', 'rules:sequence'),
880 901 ('__end__', 'rules:sequence'),
881 902 ('mandatory', 'False'),
882 903 ('__end__', 'reviewer:mapping'),
883 904 ('__end__', 'review_members:sequence'),
884 905 ('__start__', 'revisions:sequence'),
885 906 ('revisions', commit_ids['change']),
886 907 ('__end__', 'revisions:sequence'),
887 908 ('user', ''),
888 909 ('csrf_token', csrf_token),
889 910 ],
890 911 status=302)
891 912
892 913 location = response.headers['Location']
893 914
894 915 pull_request_id = location.rsplit('/', 1)[1]
895 916 assert pull_request_id != 'new'
896 917 pull_request = PullRequest.get(int(pull_request_id))
897 918
898 919 # Check that a notification was made
899 920 notifications = Notification.query()\
900 921 .filter(Notification.created_by == pull_request.author.user_id,
901 922 Notification.type_ == Notification.TYPE_PULL_REQUEST,
902 923 Notification.subject.contains(
903 924 "requested a pull request review. !%s" % pull_request_id))
904 925 assert len(notifications.all()) == 1
905 926
906 927 # Change reviewers and check that a notification was made
907 928 PullRequestModel().update_reviewers(
908 929 pull_request.pull_request_id, [
909 930 (1, [], False, 'reviewer', [])
910 931 ],
911 932 pull_request.author)
912 933 assert len(notifications.all()) == 2
913 934
914 935 def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token):
915 936 commits = [
916 937 {'message': 'ancestor',
917 938 'added': [FileNode('file_A', content='content_of_ancestor')]},
918 939 {'message': 'change',
919 940 'added': [FileNode('file_a', content='content_of_change')]},
920 941 {'message': 'change-child'},
921 942 {'message': 'ancestor-child', 'parents': ['ancestor'],
922 943 'added': [
923 944 FileNode('file_B', content='content_of_ancestor_child')]},
924 945 {'message': 'ancestor-child-2'},
925 946 ]
926 947 commit_ids = backend.create_master_repo(commits)
927 948 target = backend.create_repo(heads=['ancestor-child'])
928 949 source = backend.create_repo(heads=['change'])
929 950
930 951 response = self.app.post(
931 952 route_path('pullrequest_create', repo_name=source.repo_name),
932 953 [
933 954 ('source_repo', source.repo_name),
934 955 ('source_ref', 'branch:default:' + commit_ids['change']),
935 956 ('target_repo', target.repo_name),
936 957 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
937 958 ('common_ancestor', commit_ids['ancestor']),
938 959 ('pullrequest_title', 'Title'),
939 960 ('pullrequest_desc', 'Description'),
940 961 ('description_renderer', 'markdown'),
941 962 ('__start__', 'review_members:sequence'),
942 963 ('__start__', 'reviewer:mapping'),
943 964 ('user_id', '1'),
944 965 ('__start__', 'reasons:sequence'),
945 966 ('reason', 'Some reason'),
946 967 ('__end__', 'reasons:sequence'),
947 968 ('__start__', 'rules:sequence'),
948 969 ('__end__', 'rules:sequence'),
949 970 ('mandatory', 'False'),
950 971 ('__end__', 'reviewer:mapping'),
951 972 ('__end__', 'review_members:sequence'),
952 973 ('__start__', 'revisions:sequence'),
953 974 ('revisions', commit_ids['change']),
954 975 ('__end__', 'revisions:sequence'),
955 976 ('user', ''),
956 977 ('csrf_token', csrf_token),
957 978 ],
958 979 status=302)
959 980
960 981 location = response.headers['Location']
961 982
962 983 pull_request_id = location.rsplit('/', 1)[1]
963 984 assert pull_request_id != 'new'
964 985 pull_request = PullRequest.get(int(pull_request_id))
965 986
966 987 # target_ref has to point to the ancestor's commit_id in order to
967 988 # show the correct diff
968 989 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
969 990 assert pull_request.target_ref == expected_target_ref
970 991
971 992 # Check generated diff contents
972 993 response = response.follow()
973 994 response.mustcontain(no=['content_of_ancestor'])
974 995 response.mustcontain(no=['content_of_ancestor-child'])
975 996 response.mustcontain('content_of_change')
976 997
977 998 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
978 999 # Clear any previous calls to rcextensions
979 1000 rhodecode.EXTENSIONS.calls.clear()
980 1001
981 1002 pull_request = pr_util.create_pull_request(
982 1003 approved=True, mergeable=True)
983 1004 pull_request_id = pull_request.pull_request_id
984 1005 repo_name = pull_request.target_repo.scm_instance().name,
985 1006
986 1007 url = route_path('pullrequest_merge',
987 1008 repo_name=str(repo_name[0]),
988 1009 pull_request_id=pull_request_id)
989 1010 response = self.app.post(url, params={'csrf_token': csrf_token}).follow()
990 1011
991 1012 pull_request = PullRequest.get(pull_request_id)
992 1013
993 1014 assert response.status_int == 200
994 1015 assert pull_request.is_closed()
995 1016 assert_pull_request_status(
996 1017 pull_request, ChangesetStatus.STATUS_APPROVED)
997 1018
998 1019 # Check the relevant log entries were added
999 1020 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(3)
1000 1021 actions = [log.action for log in user_logs]
1001 1022 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
1002 1023 expected_actions = [
1003 1024 u'repo.pull_request.close',
1004 1025 u'repo.pull_request.merge',
1005 1026 u'repo.pull_request.comment.create'
1006 1027 ]
1007 1028 assert actions == expected_actions
1008 1029
1009 1030 user_logs = UserLog.query().order_by(UserLog.user_log_id.desc()).limit(4)
1010 1031 actions = [log for log in user_logs]
1011 1032 assert actions[-1].action == 'user.push'
1012 1033 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
1013 1034
1014 1035 # Check post_push rcextension was really executed
1015 1036 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
1016 1037 assert len(push_calls) == 1
1017 1038 unused_last_call_args, last_call_kwargs = push_calls[0]
1018 1039 assert last_call_kwargs['action'] == 'push'
1019 1040 assert last_call_kwargs['commit_ids'] == pr_commit_ids
1020 1041
1021 1042 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
1022 1043 pull_request = pr_util.create_pull_request(mergeable=False)
1023 1044 pull_request_id = pull_request.pull_request_id
1024 1045 pull_request = PullRequest.get(pull_request_id)
1025 1046
1026 1047 response = self.app.post(
1027 1048 route_path('pullrequest_merge',
1028 1049 repo_name=pull_request.target_repo.scm_instance().name,
1029 1050 pull_request_id=pull_request.pull_request_id),
1030 1051 params={'csrf_token': csrf_token}).follow()
1031 1052
1032 1053 assert response.status_int == 200
1033 1054 response.mustcontain(
1034 1055 'Merge is not currently possible because of below failed checks.')
1035 1056 response.mustcontain('Server-side pull request merging is disabled.')
1036 1057
1037 1058 @pytest.mark.skip_backends('svn')
1038 1059 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
1039 1060 pull_request = pr_util.create_pull_request(mergeable=True)
1040 1061 pull_request_id = pull_request.pull_request_id
1041 1062 repo_name = pull_request.target_repo.scm_instance().name
1042 1063
1043 1064 response = self.app.post(
1044 1065 route_path('pullrequest_merge',
1045 1066 repo_name=repo_name, pull_request_id=pull_request_id),
1046 1067 params={'csrf_token': csrf_token}).follow()
1047 1068
1048 1069 assert response.status_int == 200
1049 1070
1050 1071 response.mustcontain(
1051 1072 'Merge is not currently possible because of below failed checks.')
1052 1073 response.mustcontain('Pull request reviewer approval is pending.')
1053 1074
1054 1075 def test_merge_pull_request_renders_failure_reason(
1055 1076 self, user_regular, csrf_token, pr_util):
1056 1077 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
1057 1078 pull_request_id = pull_request.pull_request_id
1058 1079 repo_name = pull_request.target_repo.scm_instance().name
1059 1080
1060 1081 merge_resp = MergeResponse(True, False, 'STUB_COMMIT_ID',
1061 1082 MergeFailureReason.PUSH_FAILED,
1062 1083 metadata={'target': 'shadow repo',
1063 1084 'merge_commit': 'xxx'})
1064 1085 model_patcher = mock.patch.multiple(
1065 1086 PullRequestModel,
1066 1087 merge_repo=mock.Mock(return_value=merge_resp),
1067 1088 merge_status=mock.Mock(return_value=(None, True, 'WRONG_MESSAGE')))
1068 1089
1069 1090 with model_patcher:
1070 1091 response = self.app.post(
1071 1092 route_path('pullrequest_merge',
1072 1093 repo_name=repo_name,
1073 1094 pull_request_id=pull_request_id),
1074 1095 params={'csrf_token': csrf_token}, status=302)
1075 1096
1076 1097 merge_resp = MergeResponse(True, True, '', MergeFailureReason.PUSH_FAILED,
1077 1098 metadata={'target': 'shadow repo',
1078 1099 'merge_commit': 'xxx'})
1079 1100 assert_session_flash(response, merge_resp.merge_status_message)
1080 1101
1081 1102 def test_update_source_revision(self, backend, csrf_token):
1082 1103 commits = [
1083 1104 {'message': 'ancestor'},
1084 1105 {'message': 'change'},
1085 1106 {'message': 'change-2'},
1086 1107 ]
1087 1108 commit_ids = backend.create_master_repo(commits)
1088 1109 target = backend.create_repo(heads=['ancestor'])
1089 1110 source = backend.create_repo(heads=['change'])
1090 1111
1091 1112 # create pr from a in source to A in target
1092 1113 pull_request = PullRequest()
1093 1114
1094 1115 pull_request.source_repo = source
1095 1116 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1096 1117 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1097 1118
1098 1119 pull_request.target_repo = target
1099 1120 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1100 1121 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1101 1122
1102 1123 pull_request.revisions = [commit_ids['change']]
1103 1124 pull_request.title = u"Test"
1104 1125 pull_request.description = u"Description"
1105 1126 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1106 1127 pull_request.pull_request_state = PullRequest.STATE_CREATED
1107 1128 Session().add(pull_request)
1108 1129 Session().commit()
1109 1130 pull_request_id = pull_request.pull_request_id
1110 1131
1111 1132 # source has ancestor - change - change-2
1112 1133 backend.pull_heads(source, heads=['change-2'])
1113 1134 target_repo_name = target.repo_name
1114 1135
1115 1136 # update PR
1116 1137 self.app.post(
1117 1138 route_path('pullrequest_update',
1118 1139 repo_name=target_repo_name, pull_request_id=pull_request_id),
1119 1140 params={'update_commits': 'true', 'csrf_token': csrf_token})
1120 1141
1121 1142 response = self.app.get(
1122 1143 route_path('pullrequest_show',
1123 1144 repo_name=target_repo_name,
1124 1145 pull_request_id=pull_request.pull_request_id))
1125 1146
1126 1147 assert response.status_int == 200
1127 1148 response.mustcontain('Pull request updated to')
1128 1149 response.mustcontain('with 1 added, 0 removed commits.')
1129 1150
1130 1151 # check that we have now both revisions
1131 1152 pull_request = PullRequest.get(pull_request_id)
1132 1153 assert pull_request.revisions == [commit_ids['change-2'], commit_ids['change']]
1133 1154
1134 1155 def test_update_target_revision(self, backend, csrf_token):
1135 1156 commits = [
1136 1157 {'message': 'ancestor'},
1137 1158 {'message': 'change'},
1138 1159 {'message': 'ancestor-new', 'parents': ['ancestor']},
1139 1160 {'message': 'change-rebased'},
1140 1161 ]
1141 1162 commit_ids = backend.create_master_repo(commits)
1142 1163 target = backend.create_repo(heads=['ancestor'])
1143 1164 source = backend.create_repo(heads=['change'])
1144 1165
1145 1166 # create pr from a in source to A in target
1146 1167 pull_request = PullRequest()
1147 1168
1148 1169 pull_request.source_repo = source
1149 1170 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1150 1171 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1151 1172
1152 1173 pull_request.target_repo = target
1153 1174 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1154 1175 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1155 1176
1156 1177 pull_request.revisions = [commit_ids['change']]
1157 1178 pull_request.title = u"Test"
1158 1179 pull_request.description = u"Description"
1159 1180 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1160 1181 pull_request.pull_request_state = PullRequest.STATE_CREATED
1161 1182
1162 1183 Session().add(pull_request)
1163 1184 Session().commit()
1164 1185 pull_request_id = pull_request.pull_request_id
1165 1186
1166 1187 # target has ancestor - ancestor-new
1167 1188 # source has ancestor - ancestor-new - change-rebased
1168 1189 backend.pull_heads(target, heads=['ancestor-new'])
1169 1190 backend.pull_heads(source, heads=['change-rebased'])
1170 1191 target_repo_name = target.repo_name
1171 1192
1172 1193 # update PR
1173 1194 url = route_path('pullrequest_update',
1174 1195 repo_name=target_repo_name,
1175 1196 pull_request_id=pull_request_id)
1176 1197 self.app.post(url,
1177 1198 params={'update_commits': 'true', 'csrf_token': csrf_token},
1178 1199 status=200)
1179 1200
1180 1201 # check that we have now both revisions
1181 1202 pull_request = PullRequest.get(pull_request_id)
1182 1203 assert pull_request.revisions == [commit_ids['change-rebased']]
1183 1204 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
1184 1205 branch=backend.default_branch_name, commit_id=commit_ids['ancestor-new'])
1185 1206
1186 1207 response = self.app.get(
1187 1208 route_path('pullrequest_show',
1188 1209 repo_name=target_repo_name,
1189 1210 pull_request_id=pull_request.pull_request_id))
1190 1211 assert response.status_int == 200
1191 1212 response.mustcontain('Pull request updated to')
1192 1213 response.mustcontain('with 1 added, 1 removed commits.')
1193 1214
1194 1215 def test_update_target_revision_with_removal_of_1_commit_git(self, backend_git, csrf_token):
1195 1216 backend = backend_git
1196 1217 commits = [
1197 1218 {'message': 'master-commit-1'},
1198 1219 {'message': 'master-commit-2-change-1'},
1199 1220 {'message': 'master-commit-3-change-2'},
1200 1221
1201 1222 {'message': 'feat-commit-1', 'parents': ['master-commit-1']},
1202 1223 {'message': 'feat-commit-2'},
1203 1224 ]
1204 1225 commit_ids = backend.create_master_repo(commits)
1205 1226 target = backend.create_repo(heads=['master-commit-3-change-2'])
1206 1227 source = backend.create_repo(heads=['feat-commit-2'])
1207 1228
1208 1229 # create pr from a in source to A in target
1209 1230 pull_request = PullRequest()
1210 1231 pull_request.source_repo = source
1211 1232
1212 1233 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1213 1234 branch=backend.default_branch_name,
1214 1235 commit_id=commit_ids['master-commit-3-change-2'])
1215 1236
1216 1237 pull_request.target_repo = target
1217 1238 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1218 1239 branch=backend.default_branch_name, commit_id=commit_ids['feat-commit-2'])
1219 1240
1220 1241 pull_request.revisions = [
1221 1242 commit_ids['feat-commit-1'],
1222 1243 commit_ids['feat-commit-2']
1223 1244 ]
1224 1245 pull_request.title = u"Test"
1225 1246 pull_request.description = u"Description"
1226 1247 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1227 1248 pull_request.pull_request_state = PullRequest.STATE_CREATED
1228 1249 Session().add(pull_request)
1229 1250 Session().commit()
1230 1251 pull_request_id = pull_request.pull_request_id
1231 1252
1232 1253 # PR is created, now we simulate a force-push into target,
1233 1254 # that drops a 2 last commits
1234 1255 vcsrepo = target.scm_instance()
1235 1256 vcsrepo.config.clear_section('hooks')
1236 1257 vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2'])
1237 1258 target_repo_name = target.repo_name
1238 1259
1239 1260 # update PR
1240 1261 url = route_path('pullrequest_update',
1241 1262 repo_name=target_repo_name,
1242 1263 pull_request_id=pull_request_id)
1243 1264 self.app.post(url,
1244 1265 params={'update_commits': 'true', 'csrf_token': csrf_token},
1245 1266 status=200)
1246 1267
1247 1268 response = self.app.get(route_path('pullrequest_new', repo_name=target_repo_name))
1248 1269 assert response.status_int == 200
1249 1270 response.mustcontain('Pull request updated to')
1250 1271 response.mustcontain('with 0 added, 0 removed commits.')
1251 1272
1252 1273 def test_update_of_ancestor_reference(self, backend, csrf_token):
1253 1274 commits = [
1254 1275 {'message': 'ancestor'},
1255 1276 {'message': 'change'},
1256 1277 {'message': 'change-2'},
1257 1278 {'message': 'ancestor-new', 'parents': ['ancestor']},
1258 1279 {'message': 'change-rebased'},
1259 1280 ]
1260 1281 commit_ids = backend.create_master_repo(commits)
1261 1282 target = backend.create_repo(heads=['ancestor'])
1262 1283 source = backend.create_repo(heads=['change'])
1263 1284
1264 1285 # create pr from a in source to A in target
1265 1286 pull_request = PullRequest()
1266 1287 pull_request.source_repo = source
1267 1288
1268 1289 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1269 1290 branch=backend.default_branch_name, commit_id=commit_ids['change'])
1270 1291 pull_request.target_repo = target
1271 1292 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1272 1293 branch=backend.default_branch_name, commit_id=commit_ids['ancestor'])
1273 1294 pull_request.revisions = [commit_ids['change']]
1274 1295 pull_request.title = u"Test"
1275 1296 pull_request.description = u"Description"
1276 1297 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1277 1298 pull_request.pull_request_state = PullRequest.STATE_CREATED
1278 1299 Session().add(pull_request)
1279 1300 Session().commit()
1280 1301 pull_request_id = pull_request.pull_request_id
1281 1302
1282 1303 # target has ancestor - ancestor-new
1283 1304 # source has ancestor - ancestor-new - change-rebased
1284 1305 backend.pull_heads(target, heads=['ancestor-new'])
1285 1306 backend.pull_heads(source, heads=['change-rebased'])
1286 1307 target_repo_name = target.repo_name
1287 1308
1288 1309 # update PR
1289 1310 self.app.post(
1290 1311 route_path('pullrequest_update',
1291 1312 repo_name=target_repo_name, pull_request_id=pull_request_id),
1292 1313 params={'update_commits': 'true', 'csrf_token': csrf_token},
1293 1314 status=200)
1294 1315
1295 1316 # Expect the target reference to be updated correctly
1296 1317 pull_request = PullRequest.get(pull_request_id)
1297 1318 assert pull_request.revisions == [commit_ids['change-rebased']]
1298 1319 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
1299 1320 branch=backend.default_branch_name,
1300 1321 commit_id=commit_ids['ancestor-new'])
1301 1322 assert pull_request.target_ref == expected_target_ref
1302 1323
1303 1324 def test_remove_pull_request_branch(self, backend_git, csrf_token):
1304 1325 branch_name = 'development'
1305 1326 commits = [
1306 1327 {'message': 'initial-commit'},
1307 1328 {'message': 'old-feature'},
1308 1329 {'message': 'new-feature', 'branch': branch_name},
1309 1330 ]
1310 1331 repo = backend_git.create_repo(commits)
1311 1332 repo_name = repo.repo_name
1312 1333 commit_ids = backend_git.commit_ids
1313 1334
1314 1335 pull_request = PullRequest()
1315 1336 pull_request.source_repo = repo
1316 1337 pull_request.target_repo = repo
1317 1338 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
1318 1339 branch=branch_name, commit_id=commit_ids['new-feature'])
1319 1340 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
1320 1341 branch=backend_git.default_branch_name, commit_id=commit_ids['old-feature'])
1321 1342 pull_request.revisions = [commit_ids['new-feature']]
1322 1343 pull_request.title = u"Test"
1323 1344 pull_request.description = u"Description"
1324 1345 pull_request.author = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1325 1346 pull_request.pull_request_state = PullRequest.STATE_CREATED
1326 1347 Session().add(pull_request)
1327 1348 Session().commit()
1328 1349
1329 1350 pull_request_id = pull_request.pull_request_id
1330 1351
1331 1352 vcs = repo.scm_instance()
1332 1353 vcs.remove_ref('refs/heads/{}'.format(branch_name))
1333 1354 # NOTE(marcink): run GC to ensure the commits are gone
1334 1355 vcs.run_gc()
1335 1356
1336 1357 response = self.app.get(route_path(
1337 1358 'pullrequest_show',
1338 1359 repo_name=repo_name,
1339 1360 pull_request_id=pull_request_id))
1340 1361
1341 1362 assert response.status_int == 200
1342 1363
1343 1364 response.assert_response().element_contains(
1344 1365 '#changeset_compare_view_content .alert strong',
1345 1366 'Missing commits')
1346 1367 response.assert_response().element_contains(
1347 1368 '#changeset_compare_view_content .alert',
1348 1369 'This pull request cannot be displayed, because one or more'
1349 1370 ' commits no longer exist in the source repository.')
1350 1371
1351 1372 def test_strip_commits_from_pull_request(
1352 1373 self, backend, pr_util, csrf_token):
1353 1374 commits = [
1354 1375 {'message': 'initial-commit'},
1355 1376 {'message': 'old-feature'},
1356 1377 {'message': 'new-feature', 'parents': ['initial-commit']},
1357 1378 ]
1358 1379 pull_request = pr_util.create_pull_request(
1359 1380 commits, target_head='initial-commit', source_head='new-feature',
1360 1381 revisions=['new-feature'])
1361 1382
1362 1383 vcs = pr_util.source_repository.scm_instance()
1363 1384 if backend.alias == 'git':
1364 1385 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1365 1386 else:
1366 1387 vcs.strip(pr_util.commit_ids['new-feature'])
1367 1388
1368 1389 response = self.app.get(route_path(
1369 1390 'pullrequest_show',
1370 1391 repo_name=pr_util.target_repository.repo_name,
1371 1392 pull_request_id=pull_request.pull_request_id))
1372 1393
1373 1394 assert response.status_int == 200
1374 1395
1375 1396 response.assert_response().element_contains(
1376 1397 '#changeset_compare_view_content .alert strong',
1377 1398 'Missing commits')
1378 1399 response.assert_response().element_contains(
1379 1400 '#changeset_compare_view_content .alert',
1380 1401 'This pull request cannot be displayed, because one or more'
1381 1402 ' commits no longer exist in the source repository.')
1382 1403 response.assert_response().element_contains(
1383 1404 '#update_commits',
1384 1405 'Update commits')
1385 1406
1386 1407 def test_strip_commits_and_update(
1387 1408 self, backend, pr_util, csrf_token):
1388 1409 commits = [
1389 1410 {'message': 'initial-commit'},
1390 1411 {'message': 'old-feature'},
1391 1412 {'message': 'new-feature', 'parents': ['old-feature']},
1392 1413 ]
1393 1414 pull_request = pr_util.create_pull_request(
1394 1415 commits, target_head='old-feature', source_head='new-feature',
1395 1416 revisions=['new-feature'], mergeable=True)
1396 1417 pr_id = pull_request.pull_request_id
1397 1418 target_repo_name = pull_request.target_repo.repo_name
1398 1419
1399 1420 vcs = pr_util.source_repository.scm_instance()
1400 1421 if backend.alias == 'git':
1401 1422 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
1402 1423 else:
1403 1424 vcs.strip(pr_util.commit_ids['new-feature'])
1404 1425
1405 1426 url = route_path('pullrequest_update',
1406 1427 repo_name=target_repo_name,
1407 1428 pull_request_id=pr_id)
1408 1429 response = self.app.post(url,
1409 1430 params={'update_commits': 'true',
1410 1431 'csrf_token': csrf_token})
1411 1432
1412 1433 assert response.status_int == 200
1413 1434 assert response.body == '{"response": true, "redirect_url": null}'
1414 1435
1415 1436 # Make sure that after update, it won't raise 500 errors
1416 1437 response = self.app.get(route_path(
1417 1438 'pullrequest_show',
1418 1439 repo_name=target_repo_name,
1419 1440 pull_request_id=pr_id))
1420 1441
1421 1442 assert response.status_int == 200
1422 1443 response.assert_response().element_contains(
1423 1444 '#changeset_compare_view_content .alert strong',
1424 1445 'Missing commits')
1425 1446
1426 1447 def test_branch_is_a_link(self, pr_util):
1427 1448 pull_request = pr_util.create_pull_request()
1428 1449 pull_request.source_ref = 'branch:origin:1234567890abcdef'
1429 1450 pull_request.target_ref = 'branch:target:abcdef1234567890'
1430 1451 Session().add(pull_request)
1431 1452 Session().commit()
1432 1453
1433 1454 response = self.app.get(route_path(
1434 1455 'pullrequest_show',
1435 1456 repo_name=pull_request.target_repo.scm_instance().name,
1436 1457 pull_request_id=pull_request.pull_request_id))
1437 1458 assert response.status_int == 200
1438 1459
1439 1460 source = response.assert_response().get_element('.pr-source-info')
1440 1461 source_parent = source.getparent()
1441 1462 assert len(source_parent) == 1
1442 1463
1443 1464 target = response.assert_response().get_element('.pr-target-info')
1444 1465 target_parent = target.getparent()
1445 1466 assert len(target_parent) == 1
1446 1467
1447 1468 expected_origin_link = route_path(
1448 1469 'repo_commits',
1449 1470 repo_name=pull_request.source_repo.scm_instance().name,
1450 1471 params=dict(branch='origin'))
1451 1472 expected_target_link = route_path(
1452 1473 'repo_commits',
1453 1474 repo_name=pull_request.target_repo.scm_instance().name,
1454 1475 params=dict(branch='target'))
1455 1476 assert source_parent.attrib['href'] == expected_origin_link
1456 1477 assert target_parent.attrib['href'] == expected_target_link
1457 1478
1458 1479 def test_bookmark_is_not_a_link(self, pr_util):
1459 1480 pull_request = pr_util.create_pull_request()
1460 1481 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
1461 1482 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
1462 1483 Session().add(pull_request)
1463 1484 Session().commit()
1464 1485
1465 1486 response = self.app.get(route_path(
1466 1487 'pullrequest_show',
1467 1488 repo_name=pull_request.target_repo.scm_instance().name,
1468 1489 pull_request_id=pull_request.pull_request_id))
1469 1490 assert response.status_int == 200
1470 1491
1471 1492 source = response.assert_response().get_element('.pr-source-info')
1472 1493 assert source.text.strip() == 'bookmark:origin'
1473 1494 assert source.getparent().attrib.get('href') is None
1474 1495
1475 1496 target = response.assert_response().get_element('.pr-target-info')
1476 1497 assert target.text.strip() == 'bookmark:target'
1477 1498 assert target.getparent().attrib.get('href') is None
1478 1499
1479 1500 def test_tag_is_not_a_link(self, pr_util):
1480 1501 pull_request = pr_util.create_pull_request()
1481 1502 pull_request.source_ref = 'tag:origin:1234567890abcdef'
1482 1503 pull_request.target_ref = 'tag:target:abcdef1234567890'
1483 1504 Session().add(pull_request)
1484 1505 Session().commit()
1485 1506
1486 1507 response = self.app.get(route_path(
1487 1508 'pullrequest_show',
1488 1509 repo_name=pull_request.target_repo.scm_instance().name,
1489 1510 pull_request_id=pull_request.pull_request_id))
1490 1511 assert response.status_int == 200
1491 1512
1492 1513 source = response.assert_response().get_element('.pr-source-info')
1493 1514 assert source.text.strip() == 'tag:origin'
1494 1515 assert source.getparent().attrib.get('href') is None
1495 1516
1496 1517 target = response.assert_response().get_element('.pr-target-info')
1497 1518 assert target.text.strip() == 'tag:target'
1498 1519 assert target.getparent().attrib.get('href') is None
1499 1520
1500 1521 @pytest.mark.parametrize('mergeable', [True, False])
1501 1522 def test_shadow_repository_link(
1502 1523 self, mergeable, pr_util, http_host_only_stub):
1503 1524 """
1504 1525 Check that the pull request summary page displays a link to the shadow
1505 1526 repository if the pull request is mergeable. If it is not mergeable
1506 1527 the link should not be displayed.
1507 1528 """
1508 1529 pull_request = pr_util.create_pull_request(
1509 1530 mergeable=mergeable, enable_notifications=False)
1510 1531 target_repo = pull_request.target_repo.scm_instance()
1511 1532 pr_id = pull_request.pull_request_id
1512 1533 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
1513 1534 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
1514 1535
1515 1536 response = self.app.get(route_path(
1516 1537 'pullrequest_show',
1517 1538 repo_name=target_repo.name,
1518 1539 pull_request_id=pr_id))
1519 1540
1520 1541 if mergeable:
1521 1542 response.assert_response().element_value_contains(
1522 1543 'input.pr-mergeinfo', shadow_url)
1523 1544 response.assert_response().element_value_contains(
1524 1545 'input.pr-mergeinfo ', 'pr-merge')
1525 1546 else:
1526 1547 response.assert_response().no_element_exists('.pr-mergeinfo')
1527 1548
1528 1549
1529 1550 @pytest.mark.usefixtures('app')
1530 1551 @pytest.mark.backends("git", "hg")
1531 1552 class TestPullrequestsControllerDelete(object):
1532 1553 def test_pull_request_delete_button_permissions_admin(
1533 1554 self, autologin_user, user_admin, pr_util):
1534 1555 pull_request = pr_util.create_pull_request(
1535 1556 author=user_admin.username, enable_notifications=False)
1536 1557
1537 1558 response = self.app.get(route_path(
1538 1559 'pullrequest_show',
1539 1560 repo_name=pull_request.target_repo.scm_instance().name,
1540 1561 pull_request_id=pull_request.pull_request_id))
1541 1562
1542 1563 response.mustcontain('id="delete_pullrequest"')
1543 1564 response.mustcontain('Confirm to delete this pull request')
1544 1565
1545 1566 def test_pull_request_delete_button_permissions_owner(
1546 1567 self, autologin_regular_user, user_regular, pr_util):
1547 1568 pull_request = pr_util.create_pull_request(
1548 1569 author=user_regular.username, enable_notifications=False)
1549 1570
1550 1571 response = self.app.get(route_path(
1551 1572 'pullrequest_show',
1552 1573 repo_name=pull_request.target_repo.scm_instance().name,
1553 1574 pull_request_id=pull_request.pull_request_id))
1554 1575
1555 1576 response.mustcontain('id="delete_pullrequest"')
1556 1577 response.mustcontain('Confirm to delete this pull request')
1557 1578
1558 1579 def test_pull_request_delete_button_permissions_forbidden(
1559 1580 self, autologin_regular_user, user_regular, user_admin, pr_util):
1560 1581 pull_request = pr_util.create_pull_request(
1561 1582 author=user_admin.username, enable_notifications=False)
1562 1583
1563 1584 response = self.app.get(route_path(
1564 1585 'pullrequest_show',
1565 1586 repo_name=pull_request.target_repo.scm_instance().name,
1566 1587 pull_request_id=pull_request.pull_request_id))
1567 1588 response.mustcontain(no=['id="delete_pullrequest"'])
1568 1589 response.mustcontain(no=['Confirm to delete this pull request'])
1569 1590
1570 1591 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1571 1592 self, autologin_regular_user, user_regular, user_admin, pr_util,
1572 1593 user_util):
1573 1594
1574 1595 pull_request = pr_util.create_pull_request(
1575 1596 author=user_admin.username, enable_notifications=False)
1576 1597
1577 1598 user_util.grant_user_permission_to_repo(
1578 1599 pull_request.target_repo, user_regular,
1579 1600 'repository.write')
1580 1601
1581 1602 response = self.app.get(route_path(
1582 1603 'pullrequest_show',
1583 1604 repo_name=pull_request.target_repo.scm_instance().name,
1584 1605 pull_request_id=pull_request.pull_request_id))
1585 1606
1586 1607 response.mustcontain('id="open_edit_pullrequest"')
1587 1608 response.mustcontain('id="delete_pullrequest"')
1588 1609 response.mustcontain(no=['Confirm to delete this pull request'])
1589 1610
1590 1611 def test_delete_comment_returns_404_if_comment_does_not_exist(
1591 1612 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1592 1613
1593 1614 pull_request = pr_util.create_pull_request(
1594 1615 author=user_admin.username, enable_notifications=False)
1595 1616
1596 1617 self.app.post(
1597 1618 route_path(
1598 1619 'pullrequest_comment_delete',
1599 1620 repo_name=pull_request.target_repo.scm_instance().name,
1600 1621 pull_request_id=pull_request.pull_request_id,
1601 1622 comment_id=1024404),
1602 1623 extra_environ=xhr_header,
1603 1624 params={'csrf_token': csrf_token},
1604 1625 status=404
1605 1626 )
1606 1627
1607 1628 def test_delete_comment(
1608 1629 self, autologin_user, pr_util, user_admin, csrf_token, xhr_header):
1609 1630
1610 1631 pull_request = pr_util.create_pull_request(
1611 1632 author=user_admin.username, enable_notifications=False)
1612 1633 comment = pr_util.create_comment()
1613 1634 comment_id = comment.comment_id
1614 1635
1615 1636 response = self.app.post(
1616 1637 route_path(
1617 1638 'pullrequest_comment_delete',
1618 1639 repo_name=pull_request.target_repo.scm_instance().name,
1619 1640 pull_request_id=pull_request.pull_request_id,
1620 1641 comment_id=comment_id),
1621 1642 extra_environ=xhr_header,
1622 1643 params={'csrf_token': csrf_token},
1623 1644 status=200
1624 1645 )
1625 1646 assert response.body == 'true'
1626 1647
1627 1648 @pytest.mark.parametrize('url_type', [
1628 1649 'pullrequest_new',
1629 1650 'pullrequest_create',
1630 1651 'pullrequest_update',
1631 1652 'pullrequest_merge',
1632 1653 ])
1633 1654 def test_pull_request_is_forbidden_on_archived_repo(
1634 1655 self, autologin_user, backend, xhr_header, user_util, url_type):
1635 1656
1636 1657 # create a temporary repo
1637 1658 source = user_util.create_repo(repo_type=backend.alias)
1638 1659 repo_name = source.repo_name
1639 1660 repo = Repository.get_by_repo_name(repo_name)
1640 1661 repo.archived = True
1641 1662 Session().commit()
1642 1663
1643 1664 response = self.app.get(
1644 1665 route_path(url_type, repo_name=repo_name, pull_request_id=1), status=302)
1645 1666
1646 1667 msg = 'Action not supported for archived repository.'
1647 1668 assert_session_flash(response, msg)
1648 1669
1649 1670
1650 1671 def assert_pull_request_status(pull_request, expected_status):
1651 1672 status = ChangesetStatusModel().calculated_review_status(pull_request=pull_request)
1652 1673 assert status == expected_status
1653 1674
1654 1675
1655 1676 @pytest.mark.parametrize('route', ['pullrequest_new', 'pullrequest_create'])
1656 1677 @pytest.mark.usefixtures("autologin_user")
1657 1678 def test_forbidde_to_repo_summary_for_svn_repositories(backend_svn, app, route):
1658 1679 app.get(route_path(route, repo_name=backend_svn.repo_name), status=404)
@@ -1,293 +1,289 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import string
23 23 import time
24 24
25 25 import rhodecode
26 26
27 27
28 28
29 29 from rhodecode.lib.view_utils import get_format_ref_id
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 32 from rhodecode.lib import helpers as h, rc_cache
33 33 from rhodecode.lib.utils2 import safe_str, safe_int
34 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 37 from rhodecode.lib.vcs.exceptions import (
38 38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 39 from rhodecode.model.db import Statistics, CacheKey, User
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class RepoSummaryView(RepoAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50 c.rhodecode_repo = None
51 51 if not c.repository_requirements_missing:
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53 return c
54 54
55 55 def _load_commits_context(self, c):
56 56 p = safe_int(self.request.GET.get('page'), 1)
57 57 size = safe_int(self.request.GET.get('size'), 10)
58 58
59 59 def url_generator(page_num):
60 60 query_params = {
61 61 'page': page_num,
62 62 'size': size
63 63 }
64 64 return h.route_path(
65 65 'repo_summary_commits',
66 66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
67 67
68 68 pre_load = ['author', 'branch', 'date', 'message']
69 69 try:
70 70 collection = self.rhodecode_vcs_repo.get_commits(
71 71 pre_load=pre_load, translate_tags=False)
72 72 except EmptyRepositoryError:
73 73 collection = self.rhodecode_vcs_repo
74 74
75 75 c.repo_commits = h.RepoPage(
76 76 collection, page=p, items_per_page=size, url_maker=url_generator)
77 77 page_ids = [x.raw_id for x in c.repo_commits]
78 78 c.comments = self.db_repo.get_comments(page_ids)
79 79 c.statuses = self.db_repo.statuses(page_ids)
80 80
81 81 def _prepare_and_set_clone_url(self, c):
82 82 username = ''
83 83 if self._rhodecode_user.username != User.DEFAULT_USER:
84 84 username = safe_str(self._rhodecode_user.username)
85 85
86 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
86 _def_clone_uri = c.clone_uri_tmpl
87 _def_clone_uri_id = c.clone_uri_id_tmpl
87 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 90 c.clone_repo_url = self.db_repo.clone_url(
95 91 user=username, uri_tmpl=_def_clone_uri)
96 92 c.clone_repo_url_id = self.db_repo.clone_url(
97 93 user=username, uri_tmpl=_def_clone_uri_id)
98 94 c.clone_repo_url_ssh = self.db_repo.clone_url(
99 95 uri_tmpl=_def_clone_uri_ssh, ssh=True)
100 96
101 97 @LoginRequired()
102 98 @HasRepoPermissionAnyDecorator(
103 99 'repository.read', 'repository.write', 'repository.admin')
104 100 def summary_commits(self):
105 101 c = self.load_default_context()
106 102 self._prepare_and_set_clone_url(c)
107 103 self._load_commits_context(c)
108 104 return self._get_template_context(c)
109 105
110 106 @LoginRequired()
111 107 @HasRepoPermissionAnyDecorator(
112 108 'repository.read', 'repository.write', 'repository.admin')
113 109 def summary(self):
114 110 c = self.load_default_context()
115 111
116 112 # Prepare the clone URL
117 113 self._prepare_and_set_clone_url(c)
118 114
119 115 # If enabled, get statistics data
120 116 c.show_stats = bool(self.db_repo.enable_statistics)
121 117
122 118 stats = Session().query(Statistics) \
123 119 .filter(Statistics.repository == self.db_repo) \
124 120 .scalar()
125 121
126 122 c.stats_percentage = 0
127 123
128 124 if stats and stats.languages:
129 125 c.no_data = False is self.db_repo.enable_statistics
130 126 lang_stats_d = json.loads(stats.languages)
131 127
132 128 # Sort first by decreasing count and second by the file extension,
133 129 # so we have a consistent output.
134 130 lang_stats_items = sorted(lang_stats_d.iteritems(),
135 131 key=lambda k: (-k[1], k[0]))[:10]
136 132 lang_stats = [(x, {"count": y,
137 133 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
138 134 for x, y in lang_stats_items]
139 135
140 136 c.trending_languages = json.dumps(lang_stats)
141 137 else:
142 138 c.no_data = True
143 139 c.trending_languages = json.dumps({})
144 140
145 141 scm_model = ScmModel()
146 142 c.enable_downloads = self.db_repo.enable_downloads
147 143 c.repository_followers = scm_model.get_followers(self.db_repo)
148 144 c.repository_forks = scm_model.get_forks(self.db_repo)
149 145
150 146 # first interaction with the VCS instance after here...
151 147 if c.repository_requirements_missing:
152 148 self.request.override_renderer = \
153 149 'rhodecode:templates/summary/missing_requirements.mako'
154 150 return self._get_template_context(c)
155 151
156 152 c.readme_data, c.readme_file = \
157 153 self._get_readme_data(self.db_repo, c.visual.default_renderer)
158 154
159 155 # loads the summary commits template context
160 156 self._load_commits_context(c)
161 157
162 158 return self._get_template_context(c)
163 159
164 160 @LoginRequired()
165 161 @HasRepoPermissionAnyDecorator(
166 162 'repository.read', 'repository.write', 'repository.admin')
167 163 def repo_stats(self):
168 164 show_stats = bool(self.db_repo.enable_statistics)
169 165 repo_id = self.db_repo.repo_id
170 166
171 167 landing_commit = self.db_repo.get_landing_commit()
172 168 if isinstance(landing_commit, EmptyCommit):
173 169 return {'size': 0, 'code_stats': {}}
174 170
175 171 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
176 172 cache_on = cache_seconds > 0
177 173
178 174 log.debug(
179 175 'Computing REPO STATS for repo_id %s commit_id `%s` '
180 176 'with caching: %s[TTL: %ss]' % (
181 177 repo_id, landing_commit, cache_on, cache_seconds or 0))
182 178
183 179 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
184 180 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
185 181
186 182 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
187 183 condition=cache_on)
188 184 def compute_stats(repo_id, commit_id, _show_stats):
189 185 code_stats = {}
190 186 size = 0
191 187 try:
192 188 commit = self.db_repo.get_commit(commit_id)
193 189
194 190 for node in commit.get_filenodes_generator():
195 191 size += node.size
196 192 if not _show_stats:
197 193 continue
198 194 ext = string.lower(node.extension)
199 195 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
200 196 if ext_info:
201 197 if ext in code_stats:
202 198 code_stats[ext]['count'] += 1
203 199 else:
204 200 code_stats[ext] = {"count": 1, "desc": ext_info}
205 201 except (EmptyRepositoryError, CommitDoesNotExistError):
206 202 pass
207 203 return {'size': h.format_byte_size_binary(size),
208 204 'code_stats': code_stats}
209 205
210 206 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
211 207 return stats
212 208
213 209 @LoginRequired()
214 210 @HasRepoPermissionAnyDecorator(
215 211 'repository.read', 'repository.write', 'repository.admin')
216 212 def repo_refs_data(self):
217 213 _ = self.request.translate
218 214 self.load_default_context()
219 215
220 216 repo = self.rhodecode_vcs_repo
221 217 refs_to_create = [
222 218 (_("Branch"), repo.branches, 'branch'),
223 219 (_("Tag"), repo.tags, 'tag'),
224 220 (_("Bookmark"), repo.bookmarks, 'book'),
225 221 ]
226 222 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
227 223 data = {
228 224 'more': False,
229 225 'results': res
230 226 }
231 227 return data
232 228
233 229 @LoginRequired()
234 230 @HasRepoPermissionAnyDecorator(
235 231 'repository.read', 'repository.write', 'repository.admin')
236 232 def repo_refs_changelog_data(self):
237 233 _ = self.request.translate
238 234 self.load_default_context()
239 235
240 236 repo = self.rhodecode_vcs_repo
241 237
242 238 refs_to_create = [
243 239 (_("Branches"), repo.branches, 'branch'),
244 240 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
245 241 # TODO: enable when vcs can handle bookmarks filters
246 242 # (_("Bookmarks"), repo.bookmarks, "book"),
247 243 ]
248 244 res = self._create_reference_data(
249 245 repo, self.db_repo_name, refs_to_create)
250 246 data = {
251 247 'more': False,
252 248 'results': res
253 249 }
254 250 return data
255 251
256 252 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
257 253 format_ref_id = get_format_ref_id(repo)
258 254
259 255 result = []
260 256 for title, refs, ref_type in refs_to_create:
261 257 if refs:
262 258 result.append({
263 259 'text': title,
264 260 'children': self._create_reference_items(
265 261 repo, full_repo_name, refs, ref_type,
266 262 format_ref_id),
267 263 })
268 264 return result
269 265
270 266 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
271 267 result = []
272 268 is_svn = h.is_svn(repo)
273 269 for ref_name, raw_id in refs.iteritems():
274 270 files_url = self._create_files_url(
275 271 repo, full_repo_name, ref_name, raw_id, is_svn)
276 272 result.append({
277 273 'text': ref_name,
278 274 'id': format_ref_id(ref_name, raw_id),
279 275 'raw_id': raw_id,
280 276 'type': ref_type,
281 277 'files_url': files_url,
282 278 'idx': 0,
283 279 })
284 280 return result
285 281
286 282 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
287 283 use_commit_id = '/' in ref_name or is_svn
288 284 return h.route_path(
289 285 'repo_files',
290 286 repo_name=full_repo_name,
291 287 f_path=ref_name if is_svn else '',
292 288 commit_id=raw_id if use_commit_id else ref_name,
293 289 _query=dict(at=ref_name))
@@ -1,778 +1,782 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import sys
23 23 import logging
24 24 import collections
25 25 import tempfile
26 26 import time
27 27
28 28 from paste.gzipper import make_gzip_middleware
29 29 import pyramid.events
30 30 from pyramid.wsgi import wsgiapp
31 31 from pyramid.authorization import ACLAuthorizationPolicy
32 32 from pyramid.config import Configurator
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 36 from pyramid.renderers import render_to_response
37 37
38 38 from rhodecode.model import meta
39 39 from rhodecode.config import patches
40 40 from rhodecode.config import utils as config_utils
41 41 from rhodecode.config.environment import load_pyramid_environment
42 42
43 43 import rhodecode.events
44 44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 45 from rhodecode.lib.request import Request
46 46 from rhodecode.lib.vcs import VCSCommunicationError
47 47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 52 from rhodecode.lib.exc_tracking import store_exception
53 53 from rhodecode.subscribers import (
54 54 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 55 write_metadata_if_needed, write_usage_data)
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 def is_http_error(response):
62 62 # error which should have traceback
63 63 return response.status_code > 499
64 64
65 65
66 66 def should_load_all():
67 67 """
68 68 Returns if all application components should be loaded. In some cases it's
69 69 desired to skip apps loading for faster shell script execution
70 70 """
71 71 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 72 if ssh_cmd:
73 73 return False
74 74
75 75 return True
76 76
77 77
78 78 def make_pyramid_app(global_config, **settings):
79 79 """
80 80 Constructs the WSGI application based on Pyramid.
81 81
82 82 Specials:
83 83
84 84 * The application can also be integrated like a plugin via the call to
85 85 `includeme`. This is accompanied with the other utility functions which
86 86 are called. Changing this should be done with great care to not break
87 87 cases when these fragments are assembled from another place.
88 88
89 89 """
90 90
91 91 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
92 92 # will be replaced by the value of the environment variable "NAME" in this case.
93 93 start_time = time.time()
94 94 log.info('Pyramid app config starting')
95 95
96 96 debug = asbool(global_config.get('debug'))
97 97 if debug:
98 98 enable_debug()
99 99
100 100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101 101
102 102 global_config = _substitute_values(global_config, environ)
103 103 settings = _substitute_values(settings, environ)
104 104
105 105 sanitize_settings_and_apply_defaults(global_config, settings)
106 106
107 107 config = Configurator(settings=settings)
108 108
109 109 # Apply compatibility patches
110 110 patches.inspect_getargspec()
111 111
112 112 load_pyramid_environment(global_config, settings)
113 113
114 114 # Static file view comes first
115 115 includeme_first(config)
116 116
117 117 includeme(config)
118 118
119 119 pyramid_app = config.make_wsgi_app()
120 120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 121 pyramid_app.config = config
122 122
123 123 config.configure_celery(global_config['__file__'])
124 124
125 125 # creating the app uses a connection - return it after we are done
126 126 meta.Session.remove()
127 127 total_time = time.time() - start_time
128 128 log.info('Pyramid app `%s` created and configured in %.2fs',
129 129 pyramid_app.func_name, total_time)
130 130
131 131 return pyramid_app
132 132
133 133
134 134 def not_found_view(request):
135 135 """
136 136 This creates the view which should be registered as not-found-view to
137 137 pyramid.
138 138 """
139 139
140 140 if not getattr(request, 'vcs_call', None):
141 141 # handle like regular case with our error_handler
142 142 return error_handler(HTTPNotFound(), request)
143 143
144 144 # handle not found view as a vcs call
145 145 settings = request.registry.settings
146 146 ae_client = getattr(request, 'ae_client', None)
147 147 vcs_app = VCSMiddleware(
148 148 HTTPNotFound(), request.registry, settings,
149 149 appenlight_client=ae_client)
150 150
151 151 return wsgiapp(vcs_app)(None, request)
152 152
153 153
154 154 def error_handler(exception, request):
155 155 import rhodecode
156 156 from rhodecode.lib import helpers
157 157
158 158 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
159 159
160 160 base_response = HTTPInternalServerError()
161 161 # prefer original exception for the response since it may have headers set
162 162 if isinstance(exception, HTTPException):
163 163 base_response = exception
164 164 elif isinstance(exception, VCSCommunicationError):
165 165 base_response = VCSServerUnavailable()
166 166
167 167 if is_http_error(base_response):
168 168 log.exception(
169 169 'error occurred handling this request for path: %s', request.path)
170 170
171 171 error_explanation = base_response.explanation or str(base_response)
172 172 if base_response.status_code == 404:
173 173 error_explanation += " Optionally you don't have permission to access this page."
174 174 c = AttributeDict()
175 175 c.error_message = base_response.status
176 176 c.error_explanation = error_explanation
177 177 c.visual = AttributeDict()
178 178
179 179 c.visual.rhodecode_support_url = (
180 180 request.registry.settings.get('rhodecode_support_url') or
181 181 request.route_url('rhodecode_support')
182 182 )
183 183 c.redirect_time = 0
184 184 c.rhodecode_name = rhodecode_title
185 185 if not c.rhodecode_name:
186 186 c.rhodecode_name = 'Rhodecode'
187 187
188 188 c.causes = []
189 189 if is_http_error(base_response):
190 190 c.causes.append('Server is overloaded.')
191 191 c.causes.append('Server database connection is lost.')
192 192 c.causes.append('Server expected unhandled error.')
193 193
194 194 if hasattr(base_response, 'causes'):
195 195 c.causes = base_response.causes
196 196
197 197 c.messages = helpers.flash.pop_messages(request=request)
198 198
199 199 exc_info = sys.exc_info()
200 200 c.exception_id = id(exc_info)
201 201 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
202 202 or base_response.status_code > 499
203 203 c.exception_id_url = request.route_url(
204 204 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
205 205
206 206 if c.show_exception_id:
207 207 store_exception(c.exception_id, exc_info)
208 208
209 209 response = render_to_response(
210 210 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
211 211 response=base_response)
212 212
213 213 return response
214 214
215 215
216 216 def includeme_first(config):
217 217 # redirect automatic browser favicon.ico requests to correct place
218 218 def favicon_redirect(context, request):
219 219 return HTTPFound(
220 220 request.static_path('rhodecode:public/images/favicon.ico'))
221 221
222 222 config.add_view(favicon_redirect, route_name='favicon')
223 223 config.add_route('favicon', '/favicon.ico')
224 224
225 225 def robots_redirect(context, request):
226 226 return HTTPFound(
227 227 request.static_path('rhodecode:public/robots.txt'))
228 228
229 229 config.add_view(robots_redirect, route_name='robots')
230 230 config.add_route('robots', '/robots.txt')
231 231
232 232 config.add_static_view(
233 233 '_static/deform', 'deform:static')
234 234 config.add_static_view(
235 235 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
236 236
237 237
238 238 def includeme(config, auth_resources=None):
239 239 from rhodecode.lib.celerylib.loader import configure_celery
240 240 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
241 241 settings = config.registry.settings
242 242 config.set_request_factory(Request)
243 243
244 244 # plugin information
245 245 config.registry.rhodecode_plugins = collections.OrderedDict()
246 246
247 247 config.add_directive(
248 248 'register_rhodecode_plugin', register_rhodecode_plugin)
249 249
250 250 config.add_directive('configure_celery', configure_celery)
251 251
252 252 if asbool(settings.get('appenlight', 'false')):
253 253 config.include('appenlight_client.ext.pyramid_tween')
254 254
255 255 load_all = should_load_all()
256 256
257 257 # Includes which are required. The application would fail without them.
258 258 config.include('pyramid_mako')
259 259 config.include('rhodecode.lib.rc_beaker')
260 260 config.include('rhodecode.lib.rc_cache')
261 261 config.include('rhodecode.apps._base.navigation')
262 262 config.include('rhodecode.apps._base.subscribers')
263 263 config.include('rhodecode.tweens')
264 264 config.include('rhodecode.authentication')
265 265
266 266 if load_all:
267 267 ce_auth_resources = [
268 268 'rhodecode.authentication.plugins.auth_crowd',
269 269 'rhodecode.authentication.plugins.auth_headers',
270 270 'rhodecode.authentication.plugins.auth_jasig_cas',
271 271 'rhodecode.authentication.plugins.auth_ldap',
272 272 'rhodecode.authentication.plugins.auth_pam',
273 273 'rhodecode.authentication.plugins.auth_rhodecode',
274 274 'rhodecode.authentication.plugins.auth_token',
275 275 ]
276 276
277 277 # load CE authentication plugins
278 278
279 279 if auth_resources:
280 280 ce_auth_resources.extend(auth_resources)
281 281
282 282 for resource in ce_auth_resources:
283 283 config.include(resource)
284 284
285 285 # Auto discover authentication plugins and include their configuration.
286 286 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
287 287 from rhodecode.authentication import discover_legacy_plugins
288 288 discover_legacy_plugins(config)
289 289
290 290 # apps
291 291 if load_all:
292 292 config.include('rhodecode.api')
293 293 config.include('rhodecode.apps._base')
294 294 config.include('rhodecode.apps.hovercards')
295 295 config.include('rhodecode.apps.ops')
296 296 config.include('rhodecode.apps.channelstream')
297 297 config.include('rhodecode.apps.file_store')
298 298 config.include('rhodecode.apps.admin')
299 299 config.include('rhodecode.apps.login')
300 300 config.include('rhodecode.apps.home')
301 301 config.include('rhodecode.apps.journal')
302 302
303 303 config.include('rhodecode.apps.repository')
304 304 config.include('rhodecode.apps.repo_group')
305 305 config.include('rhodecode.apps.user_group')
306 306 config.include('rhodecode.apps.search')
307 307 config.include('rhodecode.apps.user_profile')
308 308 config.include('rhodecode.apps.user_group_profile')
309 309 config.include('rhodecode.apps.my_account')
310 310 config.include('rhodecode.apps.gist')
311 311
312 312 config.include('rhodecode.apps.svn_support')
313 313 config.include('rhodecode.apps.ssh_support')
314 314 config.include('rhodecode.apps.debug_style')
315 315
316 316 if load_all:
317 317 config.include('rhodecode.integrations')
318 318
319 319 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
320 320 config.add_translation_dirs('rhodecode:i18n/')
321 321 settings['default_locale_name'] = settings.get('lang', 'en')
322 322
323 323 # Add subscribers.
324 324 if load_all:
325 325 config.add_subscriber(scan_repositories_if_enabled,
326 326 pyramid.events.ApplicationCreated)
327 327 config.add_subscriber(write_metadata_if_needed,
328 328 pyramid.events.ApplicationCreated)
329 329 config.add_subscriber(write_usage_data,
330 330 pyramid.events.ApplicationCreated)
331 331 config.add_subscriber(write_js_routes_if_enabled,
332 332 pyramid.events.ApplicationCreated)
333 333
334 334 # request custom methods
335 335 config.add_request_method(
336 336 'rhodecode.lib.partial_renderer.get_partial_renderer',
337 337 'get_partial_renderer')
338 338
339 339 config.add_request_method(
340 340 'rhodecode.lib.request_counter.get_request_counter',
341 341 'request_count')
342 342
343 config.add_request_method(
344 'rhodecode.lib._vendor.statsd.get_statsd_client',
345 'statsd', reify=True)
346
343 347 # Set the authorization policy.
344 348 authz_policy = ACLAuthorizationPolicy()
345 349 config.set_authorization_policy(authz_policy)
346 350
347 351 # Set the default renderer for HTML templates to mako.
348 352 config.add_mako_renderer('.html')
349 353
350 354 config.add_renderer(
351 355 name='json_ext',
352 356 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
353 357
354 358 config.add_renderer(
355 359 name='string_html',
356 360 factory='rhodecode.lib.string_renderer.html')
357 361
358 362 # include RhodeCode plugins
359 363 includes = aslist(settings.get('rhodecode.includes', []))
360 364 for inc in includes:
361 365 config.include(inc)
362 366
363 367 # custom not found view, if our pyramid app doesn't know how to handle
364 368 # the request pass it to potential VCS handling ap
365 369 config.add_notfound_view(not_found_view)
366 370 if not settings.get('debugtoolbar.enabled', False):
367 371 # disabled debugtoolbar handle all exceptions via the error_handlers
368 372 config.add_view(error_handler, context=Exception)
369 373
370 374 # all errors including 403/404/50X
371 375 config.add_view(error_handler, context=HTTPError)
372 376
373 377
374 378 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
375 379 """
376 380 Apply outer WSGI middlewares around the application.
377 381 """
378 382 registry = config.registry
379 383 settings = registry.settings
380 384
381 385 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
382 386 pyramid_app = HttpsFixup(pyramid_app, settings)
383 387
384 388 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
385 389 pyramid_app, settings)
386 390 registry.ae_client = _ae_client
387 391
388 392 if settings['gzip_responses']:
389 393 pyramid_app = make_gzip_middleware(
390 394 pyramid_app, settings, compress_level=1)
391 395
392 396 # this should be the outer most middleware in the wsgi stack since
393 397 # middleware like Routes make database calls
394 398 def pyramid_app_with_cleanup(environ, start_response):
395 399 try:
396 400 return pyramid_app(environ, start_response)
397 401 finally:
398 402 # Dispose current database session and rollback uncommitted
399 403 # transactions.
400 404 meta.Session.remove()
401 405
402 406 # In a single threaded mode server, on non sqlite db we should have
403 407 # '0 Current Checked out connections' at the end of a request,
404 408 # if not, then something, somewhere is leaving a connection open
405 409 pool = meta.Base.metadata.bind.engine.pool
406 410 log.debug('sa pool status: %s', pool.status())
407 411 log.debug('Request processing finalized')
408 412
409 413 return pyramid_app_with_cleanup
410 414
411 415
412 416 def sanitize_settings_and_apply_defaults(global_config, settings):
413 417 """
414 418 Applies settings defaults and does all type conversion.
415 419
416 420 We would move all settings parsing and preparation into this place, so that
417 421 we have only one place left which deals with this part. The remaining parts
418 422 of the application would start to rely fully on well prepared settings.
419 423
420 424 This piece would later be split up per topic to avoid a big fat monster
421 425 function.
422 426 """
423 427
424 428 settings.setdefault('rhodecode.edition', 'Community Edition')
425 429 settings.setdefault('rhodecode.edition_id', 'CE')
426 430
427 431 if 'mako.default_filters' not in settings:
428 432 # set custom default filters if we don't have it defined
429 433 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
430 434 settings['mako.default_filters'] = 'h_filter'
431 435
432 436 if 'mako.directories' not in settings:
433 437 mako_directories = settings.setdefault('mako.directories', [
434 438 # Base templates of the original application
435 439 'rhodecode:templates',
436 440 ])
437 441 log.debug(
438 442 "Using the following Mako template directories: %s",
439 443 mako_directories)
440 444
441 445 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
442 446 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
443 447 raw_url = settings['beaker.session.url']
444 448 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
445 449 settings['beaker.session.url'] = 'redis://' + raw_url
446 450
447 451 # Default includes, possible to change as a user
448 452 pyramid_includes = settings.setdefault('pyramid.includes', [])
449 453 log.debug(
450 454 "Using the following pyramid.includes: %s",
451 455 pyramid_includes)
452 456
453 457 # TODO: johbo: Re-think this, usually the call to config.include
454 458 # should allow to pass in a prefix.
455 459 settings.setdefault('rhodecode.api.url', '/_admin/api')
456 460 settings.setdefault('__file__', global_config.get('__file__'))
457 461
458 462 # Sanitize generic settings.
459 463 _list_setting(settings, 'default_encoding', 'UTF-8')
460 464 _bool_setting(settings, 'is_test', 'false')
461 465 _bool_setting(settings, 'gzip_responses', 'false')
462 466
463 467 # Call split out functions that sanitize settings for each topic.
464 468 _sanitize_appenlight_settings(settings)
465 469 _sanitize_vcs_settings(settings)
466 470 _sanitize_cache_settings(settings)
467 471
468 472 # configure instance id
469 473 config_utils.set_instance_id(settings)
470 474
471 475 return settings
472 476
473 477
474 478 def enable_debug():
475 479 """
476 480 Helper to enable debug on running instance
477 481 :return:
478 482 """
479 483 import tempfile
480 484 import textwrap
481 485 import logging.config
482 486
483 487 ini_template = textwrap.dedent("""
484 488 #####################################
485 489 ### DEBUG LOGGING CONFIGURATION ####
486 490 #####################################
487 491 [loggers]
488 492 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
489 493
490 494 [handlers]
491 495 keys = console, console_sql
492 496
493 497 [formatters]
494 498 keys = generic, color_formatter, color_formatter_sql
495 499
496 500 #############
497 501 ## LOGGERS ##
498 502 #############
499 503 [logger_root]
500 504 level = NOTSET
501 505 handlers = console
502 506
503 507 [logger_sqlalchemy]
504 508 level = INFO
505 509 handlers = console_sql
506 510 qualname = sqlalchemy.engine
507 511 propagate = 0
508 512
509 513 [logger_beaker]
510 514 level = DEBUG
511 515 handlers =
512 516 qualname = beaker.container
513 517 propagate = 1
514 518
515 519 [logger_rhodecode]
516 520 level = DEBUG
517 521 handlers =
518 522 qualname = rhodecode
519 523 propagate = 1
520 524
521 525 [logger_ssh_wrapper]
522 526 level = DEBUG
523 527 handlers =
524 528 qualname = ssh_wrapper
525 529 propagate = 1
526 530
527 531 [logger_celery]
528 532 level = DEBUG
529 533 handlers =
530 534 qualname = celery
531 535
532 536
533 537 ##############
534 538 ## HANDLERS ##
535 539 ##############
536 540
537 541 [handler_console]
538 542 class = StreamHandler
539 543 args = (sys.stderr, )
540 544 level = DEBUG
541 545 formatter = color_formatter
542 546
543 547 [handler_console_sql]
544 548 # "level = DEBUG" logs SQL queries and results.
545 549 # "level = INFO" logs SQL queries.
546 550 # "level = WARN" logs neither. (Recommended for production systems.)
547 551 class = StreamHandler
548 552 args = (sys.stderr, )
549 553 level = WARN
550 554 formatter = color_formatter_sql
551 555
552 556 ################
553 557 ## FORMATTERS ##
554 558 ################
555 559
556 560 [formatter_generic]
557 561 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
558 562 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
559 563 datefmt = %Y-%m-%d %H:%M:%S
560 564
561 565 [formatter_color_formatter]
562 566 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
563 567 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
564 568 datefmt = %Y-%m-%d %H:%M:%S
565 569
566 570 [formatter_color_formatter_sql]
567 571 class = rhodecode.lib.logging_formatter.ColorFormatterSql
568 572 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
569 573 datefmt = %Y-%m-%d %H:%M:%S
570 574 """)
571 575
572 576 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
573 577 delete=False) as f:
574 578 log.info('Saved Temporary DEBUG config at %s', f.name)
575 579 f.write(ini_template)
576 580
577 581 logging.config.fileConfig(f.name)
578 582 log.debug('DEBUG MODE ON')
579 583 os.remove(f.name)
580 584
581 585
582 586 def _sanitize_appenlight_settings(settings):
583 587 _bool_setting(settings, 'appenlight', 'false')
584 588
585 589
586 590 def _sanitize_vcs_settings(settings):
587 591 """
588 592 Applies settings defaults and does type conversion for all VCS related
589 593 settings.
590 594 """
591 595 _string_setting(settings, 'vcs.svn.compatible_version', '')
592 596 _string_setting(settings, 'vcs.hooks.protocol', 'http')
593 597 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
594 598 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
595 599 _string_setting(settings, 'vcs.server', '')
596 600 _string_setting(settings, 'vcs.server.protocol', 'http')
597 601 _bool_setting(settings, 'startup.import_repos', 'false')
598 602 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
599 603 _bool_setting(settings, 'vcs.server.enable', 'true')
600 604 _bool_setting(settings, 'vcs.start_server', 'false')
601 605 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
602 606 _int_setting(settings, 'vcs.connection_timeout', 3600)
603 607
604 608 # Support legacy values of vcs.scm_app_implementation. Legacy
605 609 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
606 610 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
607 611 scm_app_impl = settings['vcs.scm_app_implementation']
608 612 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
609 613 settings['vcs.scm_app_implementation'] = 'http'
610 614
611 615
612 616 def _sanitize_cache_settings(settings):
613 617 temp_store = tempfile.gettempdir()
614 618 default_cache_dir = os.path.join(temp_store, 'rc_cache')
615 619
616 620 # save default, cache dir, and use it for all backends later.
617 621 default_cache_dir = _string_setting(
618 622 settings,
619 623 'cache_dir',
620 624 default_cache_dir, lower=False, default_when_empty=True)
621 625
622 626 # ensure we have our dir created
623 627 if not os.path.isdir(default_cache_dir):
624 628 os.makedirs(default_cache_dir, mode=0o755)
625 629
626 630 # exception store cache
627 631 _string_setting(
628 632 settings,
629 633 'exception_tracker.store_path',
630 634 temp_store, lower=False, default_when_empty=True)
631 635 _bool_setting(
632 636 settings,
633 637 'exception_tracker.send_email',
634 638 'false')
635 639 _string_setting(
636 640 settings,
637 641 'exception_tracker.email_prefix',
638 642 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
639 643
640 644 # cache_perms
641 645 _string_setting(
642 646 settings,
643 647 'rc_cache.cache_perms.backend',
644 648 'dogpile.cache.rc.file_namespace', lower=False)
645 649 _int_setting(
646 650 settings,
647 651 'rc_cache.cache_perms.expiration_time',
648 652 60)
649 653 _string_setting(
650 654 settings,
651 655 'rc_cache.cache_perms.arguments.filename',
652 656 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
653 657
654 658 # cache_repo
655 659 _string_setting(
656 660 settings,
657 661 'rc_cache.cache_repo.backend',
658 662 'dogpile.cache.rc.file_namespace', lower=False)
659 663 _int_setting(
660 664 settings,
661 665 'rc_cache.cache_repo.expiration_time',
662 666 60)
663 667 _string_setting(
664 668 settings,
665 669 'rc_cache.cache_repo.arguments.filename',
666 670 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
667 671
668 672 # cache_license
669 673 _string_setting(
670 674 settings,
671 675 'rc_cache.cache_license.backend',
672 676 'dogpile.cache.rc.file_namespace', lower=False)
673 677 _int_setting(
674 678 settings,
675 679 'rc_cache.cache_license.expiration_time',
676 680 5*60)
677 681 _string_setting(
678 682 settings,
679 683 'rc_cache.cache_license.arguments.filename',
680 684 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
681 685
682 686 # cache_repo_longterm memory, 96H
683 687 _string_setting(
684 688 settings,
685 689 'rc_cache.cache_repo_longterm.backend',
686 690 'dogpile.cache.rc.memory_lru', lower=False)
687 691 _int_setting(
688 692 settings,
689 693 'rc_cache.cache_repo_longterm.expiration_time',
690 694 345600)
691 695 _int_setting(
692 696 settings,
693 697 'rc_cache.cache_repo_longterm.max_size',
694 698 10000)
695 699
696 700 # sql_cache_short
697 701 _string_setting(
698 702 settings,
699 703 'rc_cache.sql_cache_short.backend',
700 704 'dogpile.cache.rc.memory_lru', lower=False)
701 705 _int_setting(
702 706 settings,
703 707 'rc_cache.sql_cache_short.expiration_time',
704 708 30)
705 709 _int_setting(
706 710 settings,
707 711 'rc_cache.sql_cache_short.max_size',
708 712 10000)
709 713
710 714
711 715 def _int_setting(settings, name, default):
712 716 settings[name] = int(settings.get(name, default))
713 717 return settings[name]
714 718
715 719
716 720 def _bool_setting(settings, name, default):
717 721 input_val = settings.get(name, default)
718 722 if isinstance(input_val, unicode):
719 723 input_val = input_val.encode('utf8')
720 724 settings[name] = asbool(input_val)
721 725 return settings[name]
722 726
723 727
724 728 def _list_setting(settings, name, default):
725 729 raw_value = settings.get(name, default)
726 730
727 731 old_separator = ','
728 732 if old_separator in raw_value:
729 733 # If we get a comma separated list, pass it to our own function.
730 734 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
731 735 else:
732 736 # Otherwise we assume it uses pyramids space/newline separation.
733 737 settings[name] = aslist(raw_value)
734 738 return settings[name]
735 739
736 740
737 741 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
738 742 value = settings.get(name, default)
739 743
740 744 if default_when_empty and not value:
741 745 # use default value when value is empty
742 746 value = default
743 747
744 748 if lower:
745 749 value = value.lower()
746 750 settings[name] = value
747 751 return settings[name]
748 752
749 753
750 754 def _substitute_values(mapping, substitutions):
751 755 result = {}
752 756
753 757 try:
754 758 for key, value in mapping.items():
755 759 # initialize without substitution first
756 760 result[key] = value
757 761
758 762 # Note: Cannot use regular replacements, since they would clash
759 763 # with the implementation of ConfigParser. Using "format" instead.
760 764 try:
761 765 result[key] = value.format(**substitutions)
762 766 except KeyError as e:
763 767 env_var = '{}'.format(e.args[0])
764 768
765 769 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
766 770 'Make sure your environment has {var} set, or remove this ' \
767 771 'variable from config file'.format(key=key, var=env_var)
768 772
769 773 if env_var.startswith('ENV_'):
770 774 raise ValueError(msg)
771 775 else:
772 776 log.warning(msg)
773 777
774 778 except ValueError as e:
775 779 log.warning('Failed to substitute ENV variable: %s', e)
776 780 result = mapping
777 781
778 782 return result
This diff has been collapsed as it changes many lines, (1706 lines changed) Show them Hide them
@@ -1,11614 +1,11666 b''
1 1 # Translations template for rhodecode-enterprise-ce.
2 # Copyright (C) 2020 RhodeCode GmbH
2 # Copyright (C) 2021 RhodeCode GmbH
3 3 # This file is distributed under the same license as the rhodecode-enterprise-ce project.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
5 5 #
6 6 #, fuzzy
7 7 msgid ""
8 8 msgstr ""
9 "Project-Id-Version: rhodecode-enterprise-ce 4.23.0\n"
9 "Project-Id-Version: rhodecode-enterprise-ce 4.24.0\n"
10 10 "Report-Msgid-Bugs-To: marcin@rhodecode.com\n"
11 "POT-Creation-Date: 2020-11-23 09:00+0000\n"
11 "POT-Creation-Date: 2021-01-14 15:36+0000\n"
12 12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 14 "Language-Team: LANGUAGE <LL@li.org>\n"
15 15 "MIME-Version: 1.0\n"
16 16 "Content-Type: text/plain; charset=utf-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Generated-By: Babel 1.3\n"
19 19
20 20 #: rhodecode/api/views/pull_request_api.py:646
21 21 #: rhodecode/api/views/repo_api.py:1686
22 22 msgid "posted a new {} comment"
23 23 msgstr ""
24 24
25 #: rhodecode/apps/admin/views/defaults.py:90
25 #: rhodecode/apps/admin/views/defaults.py:82
26 26 msgid "Default settings updated successfully"
27 27 msgstr ""
28 28
29 #: rhodecode/apps/admin/views/defaults.py:108
29 #: rhodecode/apps/admin/views/defaults.py:100
30 30 msgid "Error occurred during update of default values"
31 31 msgstr ""
32 32
33 #: rhodecode/apps/admin/views/exception_tracker.py:156
33 #: rhodecode/apps/admin/views/exception_tracker.py:146
34 34 msgid "Removed {} Exceptions"
35 35 msgstr ""
36 36
37 #: rhodecode/apps/admin/views/exception_tracker.py:173
37 #: rhodecode/apps/admin/views/exception_tracker.py:160
38 38 msgid "Removed Exception {}"
39 39 msgstr ""
40 40
41 #: rhodecode/apps/admin/views/permissions.py:121
41 #: rhodecode/apps/admin/views/permissions.py:114
42 42 msgid "Application permissions updated successfully"
43 43 msgstr ""
44 44
45 #: rhodecode/apps/admin/views/permissions.py:142
46 #: rhodecode/apps/admin/views/permissions.py:218
47 #: rhodecode/apps/admin/views/permissions.py:320
45 #: rhodecode/apps/admin/views/permissions.py:135
46 #: rhodecode/apps/admin/views/permissions.py:205
47 #: rhodecode/apps/admin/views/permissions.py:298
48 48 msgid "Error occurred during update of permissions"
49 49 msgstr ""
50 50
51 #: rhodecode/apps/admin/views/permissions.py:198
51 #: rhodecode/apps/admin/views/permissions.py:185
52 52 msgid "Object permissions updated successfully"
53 53 msgstr ""
54 54
55 #: rhodecode/apps/admin/views/permissions.py:300
55 #: rhodecode/apps/admin/views/permissions.py:278
56 56 msgid "Global permissions updated successfully"
57 57 msgstr ""
58 58
59 #: rhodecode/apps/admin/views/permissions.py:485
59 #: rhodecode/apps/admin/views/permissions.py:448
60 60 #: rhodecode/templates/admin/gists/gist_show.mako:50
61 61 #: rhodecode/templates/admin/integrations/list.mako:172
62 62 #: rhodecode/templates/admin/my_account/my_account_profile.mako:7
63 63 #: rhodecode/templates/base/issue_tracker_settings.mako:138
64 64 #: rhodecode/templates/changeset/changeset_file_comment.mako:233
65 65 #: rhodecode/templates/changeset/changeset_file_comment.mako:247
66 66 #: rhodecode/templates/changeset/changeset_file_comment.mako:256
67 67 #: rhodecode/templates/data_table/_dt_elements.mako:173
68 68 #: rhodecode/templates/data_table/_dt_elements.mako:251
69 69 #: rhodecode/templates/data_table/_dt_elements.mako:266
70 70 #: rhodecode/templates/data_table/_dt_elements.mako:267
71 71 #: rhodecode/templates/data_table/_dt_elements.mako:282
72 72 #: rhodecode/templates/debug_style/buttons.html:128
73 73 #: rhodecode/templates/files/files_add.mako:55
74 74 #: rhodecode/templates/files/files_edit.mako:57
75 75 #: rhodecode/templates/files/files_source.mako:39
76 76 #: rhodecode/templates/files/files_source.mako:52
77 77 #: rhodecode/templates/pullrequests/pullrequest_show.mako:81
78 #: rhodecode/templates/pullrequests/pullrequest_show.mako:622
79 #: rhodecode/templates/pullrequests/pullrequest_show.mako:679
78 #: rhodecode/templates/pullrequests/pullrequest_show.mako:609
79 #: rhodecode/templates/pullrequests/pullrequest_show.mako:675
80 80 #: rhodecode/templates/user_group/profile.mako:8
81 81 #: rhodecode/templates/users/user_profile.mako:8
82 82 msgid "Edit"
83 83 msgstr ""
84 84
85 #: rhodecode/apps/admin/views/permissions.py:513
85 #: rhodecode/apps/admin/views/permissions.py:473
86 86 msgid "Updated SSH keys file: {}"
87 87 msgstr ""
88 88
89 #: rhodecode/apps/admin/views/permissions.py:516
89 #: rhodecode/apps/admin/views/permissions.py:476
90 90 msgid "SSH key support is disabled in .ini file"
91 91 msgstr ""
92 92
93 #: rhodecode/apps/admin/views/repo_groups.py:342
93 #: rhodecode/apps/admin/views/repo_groups.py:330
94 94 #, python-format
95 95 msgid "Created repository group %s"
96 96 msgstr ""
97 97
98 #: rhodecode/apps/admin/views/repo_groups.py:360
98 #: rhodecode/apps/admin/views/repo_groups.py:348
99 99 #, python-format
100 100 msgid "Error occurred during creation of repository group %s"
101 101 msgstr ""
102 102
103 #: rhodecode/apps/admin/views/sessions.py:92
103 #: rhodecode/apps/admin/views/sessions.py:86
104 104 msgid "Cleaned up old sessions"
105 105 msgstr ""
106 106
107 #: rhodecode/apps/admin/views/sessions.py:97
107 #: rhodecode/apps/admin/views/sessions.py:91
108 108 msgid "Failed to cleanup up old sessions"
109 109 msgstr ""
110 110
111 #: rhodecode/apps/admin/views/settings.py:163
112 #: rhodecode/apps/admin/views/settings.py:319
113 #: rhodecode/apps/admin/views/settings.py:394
114 #: rhodecode/apps/admin/views/settings.py:736
115 #: rhodecode/apps/repository/views/repo_settings_vcs.py:124
111 #: rhodecode/apps/admin/views/settings.py:156
112 #: rhodecode/apps/admin/views/settings.py:291
113 #: rhodecode/apps/admin/views/settings.py:360
114 #: rhodecode/apps/admin/views/settings.py:663
115 #: rhodecode/apps/repository/views/repo_settings_vcs.py:116
116 116 msgid "Some form inputs contain invalid data."
117 117 msgstr ""
118 118
119 #: rhodecode/apps/admin/views/settings.py:190
120 #: rhodecode/apps/admin/views/settings.py:355
119 #: rhodecode/apps/admin/views/settings.py:183
120 #: rhodecode/apps/admin/views/settings.py:327
121 121 msgid "Error occurred during updating application settings"
122 122 msgstr ""
123 123
124 #: rhodecode/apps/admin/views/settings.py:194
125 #: rhodecode/apps/repository/views/repo_settings_vcs.py:143
124 #: rhodecode/apps/admin/views/settings.py:187
125 #: rhodecode/apps/repository/views/repo_settings_vcs.py:135
126 126 msgid "Updated VCS settings"
127 127 msgstr ""
128 128
129 #: rhodecode/apps/admin/views/settings.py:269
129 #: rhodecode/apps/admin/views/settings.py:253
130 130 #, python-format
131 131 msgid "Repositories successfully rescanned added: %s ; removed: %s"
132 132 msgstr ""
133 133
134 #: rhodecode/apps/admin/views/settings.py:351
134 #: rhodecode/apps/admin/views/settings.py:323
135 135 msgid "Updated application settings"
136 136 msgstr ""
137 137
138 #: rhodecode/apps/admin/views/settings.py:433
138 #: rhodecode/apps/admin/views/settings.py:399
139 139 msgid "Updated visualisation settings"
140 140 msgstr ""
141 141
142 #: rhodecode/apps/admin/views/settings.py:436
142 #: rhodecode/apps/admin/views/settings.py:402
143 143 msgid "Error occurred during updating visualisation settings"
144 144 msgstr ""
145 145
146 #: rhodecode/apps/admin/views/settings.py:507
147 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:127
146 #: rhodecode/apps/admin/views/settings.py:464
147 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:115
148 148 msgid "Invalid issue tracker pattern: {}"
149 149 msgstr ""
150 150
151 #: rhodecode/apps/admin/views/settings.py:524
152 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:136
151 #: rhodecode/apps/admin/views/settings.py:481
152 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:124
153 153 msgid "Updated issue tracker entries"
154 154 msgstr ""
155 155
156 #: rhodecode/apps/admin/views/settings.py:544
157 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:91
156 #: rhodecode/apps/admin/views/settings.py:498
157 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:82
158 158 msgid "Removed issue tracker entry."
159 159 msgstr ""
160 160
161 #: rhodecode/apps/admin/views/settings.py:582
161 #: rhodecode/apps/admin/views/settings.py:530
162 162 msgid "Please enter email address"
163 163 msgstr ""
164 164
165 #: rhodecode/apps/admin/views/settings.py:598
165 #: rhodecode/apps/admin/views/settings.py:546
166 166 msgid "Send email task created"
167 167 msgstr ""
168 168
169 #: rhodecode/apps/admin/views/settings.py:648
169 #: rhodecode/apps/admin/views/settings.py:587
170 170 msgid "Added new hook"
171 171 msgstr ""
172 172
173 #: rhodecode/apps/admin/views/settings.py:663
173 #: rhodecode/apps/admin/views/settings.py:602
174 174 msgid "Updated hooks"
175 175 msgstr ""
176 176
177 #: rhodecode/apps/admin/views/settings.py:667
177 #: rhodecode/apps/admin/views/settings.py:606
178 178 msgid "Error occurred during hook creation"
179 179 msgstr ""
180 180
181 #: rhodecode/apps/admin/views/settings.py:760
181 #: rhodecode/apps/admin/views/settings.py:687
182 182 msgid "Error occurred during updating labs settings"
183 183 msgstr ""
184 184
185 #: rhodecode/apps/admin/views/settings.py:765
185 #: rhodecode/apps/admin/views/settings.py:692
186 186 msgid "Updated Labs settings"
187 187 msgstr ""
188 188
189 #: rhodecode/apps/admin/views/svn_config.py:46
189 #: rhodecode/apps/admin/views/svn_config.py:43
190 190 msgid "Apache configuration for Subversion generated at `{}`."
191 191 msgstr ""
192 192
193 #: rhodecode/apps/admin/views/svn_config.py:54
193 #: rhodecode/apps/admin/views/svn_config.py:51
194 194 msgid "Failed to generate the Apache configuration for Subversion."
195 195 msgstr ""
196 196
197 #: rhodecode/apps/admin/views/system_info.py:79
197 #: rhodecode/apps/admin/views/system_info.py:76
198 198 msgid "Note: please make sure this server can access `${url}` for the update link to work"
199 199 msgstr ""
200 200
201 #: rhodecode/apps/admin/views/system_info.py:90
201 #: rhodecode/apps/admin/views/system_info.py:87
202 202 msgid "Update info"
203 203 msgstr ""
204 204
205 #: rhodecode/apps/admin/views/system_info.py:92
205 #: rhodecode/apps/admin/views/system_info.py:89
206 206 msgid "Check for updates"
207 207 msgstr ""
208 208
209 #: rhodecode/apps/admin/views/system_info.py:94
210 msgid "RhodeCode Version"
211 msgstr ""
212
213 #: rhodecode/apps/admin/views/system_info.py:95
214 msgid "Latest version"
215 msgstr ""
216
217 #: rhodecode/apps/admin/views/system_info.py:96
218 msgid "RhodeCode Base URL"
219 msgstr ""
220
209 221 #: rhodecode/apps/admin/views/system_info.py:97
210 msgid "RhodeCode Version"
222 msgid "RhodeCode Server IP"
211 223 msgstr ""
212 224
213 225 #: rhodecode/apps/admin/views/system_info.py:98
214 msgid "Latest version"
226 msgid "RhodeCode Server ID"
215 227 msgstr ""
216 228
217 229 #: rhodecode/apps/admin/views/system_info.py:99
218 msgid "RhodeCode Base URL"
230 msgid "RhodeCode Configuration"
219 231 msgstr ""
220 232
221 233 #: rhodecode/apps/admin/views/system_info.py:100
222 msgid "RhodeCode Server IP"
234 msgid "RhodeCode Certificate"
223 235 msgstr ""
224 236
225 237 #: rhodecode/apps/admin/views/system_info.py:101
226 msgid "RhodeCode Server ID"
238 msgid "Workers"
227 239 msgstr ""
228 240
229 241 #: rhodecode/apps/admin/views/system_info.py:102
230 msgid "RhodeCode Configuration"
231 msgstr ""
232
233 #: rhodecode/apps/admin/views/system_info.py:103
234 msgid "RhodeCode Certificate"
235 msgstr ""
236
237 #: rhodecode/apps/admin/views/system_info.py:104
238 msgid "Workers"
239 msgstr ""
240
241 #: rhodecode/apps/admin/views/system_info.py:105
242 242 msgid "Worker Type"
243 243 msgstr ""
244 244
245 #: rhodecode/apps/admin/views/system_info.py:109
245 #: rhodecode/apps/admin/views/system_info.py:106
246 246 msgid "Database"
247 247 msgstr ""
248 248
249 #: rhodecode/apps/admin/views/system_info.py:110
249 #: rhodecode/apps/admin/views/system_info.py:107
250 250 msgid "Database version"
251 251 msgstr ""
252 252
253 #: rhodecode/apps/admin/views/system_info.py:111
254 msgid "Platform"
255 msgstr ""
256
257 #: rhodecode/apps/admin/views/system_info.py:112
258 msgid "Platform UUID"
259 msgstr ""
260
261 #: rhodecode/apps/admin/views/system_info.py:113
262 msgid "Lang"
263 msgstr ""
264
253 265 #: rhodecode/apps/admin/views/system_info.py:114
254 msgid "Platform"
266 msgid "Python version"
255 267 msgstr ""
256 268
257 269 #: rhodecode/apps/admin/views/system_info.py:115
258 msgid "Platform UUID"
259 msgstr ""
260
261 #: rhodecode/apps/admin/views/system_info.py:116
262 msgid "Lang"
263 msgstr ""
264
265 #: rhodecode/apps/admin/views/system_info.py:117
266 msgid "Python version"
267 msgstr ""
268
269 #: rhodecode/apps/admin/views/system_info.py:118
270 270 msgid "Python path"
271 271 msgstr ""
272 272
273 #: rhodecode/apps/admin/views/system_info.py:119
274 msgid "CPU"
275 msgstr ""
276
277 #: rhodecode/apps/admin/views/system_info.py:120
278 msgid "Load"
279 msgstr ""
280
281 #: rhodecode/apps/admin/views/system_info.py:121
282 msgid "Memory"
283 msgstr ""
284
273 285 #: rhodecode/apps/admin/views/system_info.py:122
274 msgid "CPU"
275 msgstr ""
276
277 #: rhodecode/apps/admin/views/system_info.py:123
278 msgid "Load"
279 msgstr ""
280
281 #: rhodecode/apps/admin/views/system_info.py:124
282 msgid "Memory"
283 msgstr ""
284
285 #: rhodecode/apps/admin/views/system_info.py:125
286 286 msgid "Uptime"
287 287 msgstr ""
288 288
289 #: rhodecode/apps/admin/views/system_info.py:126
290 msgid "Ulimit"
291 msgstr ""
292
289 293 #: rhodecode/apps/admin/views/system_info.py:129
290 msgid "Ulimit"
291 msgstr ""
292
293 #: rhodecode/apps/admin/views/system_info.py:132
294 294 msgid "Storage location"
295 295 msgstr ""
296 296
297 #: rhodecode/apps/admin/views/system_info.py:130
298 msgid "Storage info"
299 msgstr ""
300
301 #: rhodecode/apps/admin/views/system_info.py:131
302 msgid "Storage inodes"
303 msgstr ""
304
297 305 #: rhodecode/apps/admin/views/system_info.py:133
298 msgid "Storage info"
306 msgid "Gist storage location"
299 307 msgstr ""
300 308
301 309 #: rhodecode/apps/admin/views/system_info.py:134
302 msgid "Storage inodes"
310 msgid "Gist storage info"
303 311 msgstr ""
304 312
305 313 #: rhodecode/apps/admin/views/system_info.py:136
306 msgid "Gist storage location"
314 msgid "Archive cache storage location"
307 315 msgstr ""
308 316
309 317 #: rhodecode/apps/admin/views/system_info.py:137
310 msgid "Gist storage info"
318 msgid "Archive cache info"
311 319 msgstr ""
312 320
313 321 #: rhodecode/apps/admin/views/system_info.py:139
314 msgid "Archive cache storage location"
322 msgid "Temp storage location"
315 323 msgstr ""
316 324
317 325 #: rhodecode/apps/admin/views/system_info.py:140
318 msgid "Archive cache info"
326 msgid "Temp storage info"
319 327 msgstr ""
320 328
321 329 #: rhodecode/apps/admin/views/system_info.py:142
322 msgid "Temp storage location"
330 msgid "Search info"
323 331 msgstr ""
324 332
325 333 #: rhodecode/apps/admin/views/system_info.py:143
326 msgid "Temp storage info"
327 msgstr ""
328
329 #: rhodecode/apps/admin/views/system_info.py:145
330 msgid "Search info"
331 msgstr ""
332
333 #: rhodecode/apps/admin/views/system_info.py:146
334 334 msgid "Search location"
335 335 msgstr ""
336 336
337 #: rhodecode/apps/admin/views/system_info.py:147
338 msgid "VCS Backends"
339 msgstr ""
340
341 #: rhodecode/apps/admin/views/system_info.py:148
342 #: rhodecode/templates/admin/settings/settings_system.mako:32
343 msgid "VCS Server"
344 msgstr ""
345
346 #: rhodecode/apps/admin/views/system_info.py:149
347 msgid "GIT"
348 msgstr ""
349
337 350 #: rhodecode/apps/admin/views/system_info.py:150
338 msgid "VCS Backends"
351 msgid "HG"
339 352 msgstr ""
340 353
341 354 #: rhodecode/apps/admin/views/system_info.py:151
342 #: rhodecode/templates/admin/settings/settings_system.mako:32
343 msgid "VCS Server"
344 msgstr ""
345
346 #: rhodecode/apps/admin/views/system_info.py:152
347 msgid "GIT"
348 msgstr ""
349
350 #: rhodecode/apps/admin/views/system_info.py:153
351 msgid "HG"
352 msgstr ""
353
354 #: rhodecode/apps/admin/views/system_info.py:154
355 355 msgid "SVN"
356 356 msgstr ""
357 357
358 #: rhodecode/apps/admin/views/user_groups.py:238
358 #: rhodecode/apps/admin/views/user_groups.py:224
359 359 #, python-format
360 360 msgid "Created user group %(user_group_link)s"
361 361 msgstr ""
362 362
363 #: rhodecode/apps/admin/views/user_groups.py:260
363 #: rhodecode/apps/admin/views/user_groups.py:246
364 364 #, python-format
365 365 msgid "Error occurred during creation of user group %s"
366 366 msgstr ""
367 367
368 #: rhodecode/apps/admin/views/users.py:222
368 #: rhodecode/apps/admin/views/users.py:208
369 369 #, python-format
370 370 msgid "Created user %(user_link)s"
371 371 msgstr ""
372 372
373 #: rhodecode/apps/admin/views/users.py:243
373 #: rhodecode/apps/admin/views/users.py:229
374 374 #, python-format
375 375 msgid "Error occurred during creation of user %s"
376 376 msgstr ""
377 377
378 #: rhodecode/apps/admin/views/users.py:349
378 #: rhodecode/apps/admin/views/users.py:332
379 379 msgid "User updated successfully"
380 380 msgstr ""
381 381
382 #: rhodecode/apps/admin/views/users.py:367
382 #: rhodecode/apps/admin/views/users.py:350
383 383 #, python-format
384 384 msgid "Error occurred during update of user %s"
385 385 msgstr ""
386 386
387 #: rhodecode/apps/admin/views/users.py:398
387 #: rhodecode/apps/admin/views/users.py:378
388 388 #, python-format
389 389 msgid "Detached %s repositories"
390 390 msgstr ""
391 391
392 #: rhodecode/apps/admin/views/users.py:401
392 #: rhodecode/apps/admin/views/users.py:381
393 393 #, python-format
394 394 msgid "Deleted %s repositories"
395 395 msgstr ""
396 396
397 #: rhodecode/apps/admin/views/users.py:407
397 #: rhodecode/apps/admin/views/users.py:387
398 398 #, python-format
399 399 msgid "Detached %s repository groups"
400 400 msgstr ""
401 401
402 #: rhodecode/apps/admin/views/users.py:410
402 #: rhodecode/apps/admin/views/users.py:390
403 403 #, python-format
404 404 msgid "Deleted %s repository groups"
405 405 msgstr ""
406 406
407 #: rhodecode/apps/admin/views/users.py:416
407 #: rhodecode/apps/admin/views/users.py:396
408 408 #, python-format
409 409 msgid "Detached %s user groups"
410 410 msgstr ""
411 411
412 #: rhodecode/apps/admin/views/users.py:419
412 #: rhodecode/apps/admin/views/users.py:399
413 413 #, python-format
414 414 msgid "Deleted %s user groups"
415 415 msgstr ""
416 416
417 #: rhodecode/apps/admin/views/users.py:425
417 #: rhodecode/apps/admin/views/users.py:405
418 418 #, python-format
419 419 msgid "Detached %s pull requests"
420 420 msgstr ""
421 421
422 #: rhodecode/apps/admin/views/users.py:428
422 #: rhodecode/apps/admin/views/users.py:408
423 423 #, python-format
424 424 msgid "Deleted %s pull requests"
425 425 msgstr ""
426 426
427 #: rhodecode/apps/admin/views/users.py:434
427 #: rhodecode/apps/admin/views/users.py:414
428 428 #, python-format
429 429 msgid "Detached %s artifacts"
430 430 msgstr ""
431 431
432 #: rhodecode/apps/admin/views/users.py:437
432 #: rhodecode/apps/admin/views/users.py:417
433 433 #, python-format
434 434 msgid "Deleted %s artifacts"
435 435 msgstr ""
436 436
437 #: rhodecode/apps/admin/views/users.py:486
437 #: rhodecode/apps/admin/views/users.py:466
438 438 msgid "Successfully deleted user `{}`"
439 439 msgstr ""
440 440
441 #: rhodecode/apps/admin/views/users.py:493
441 #: rhodecode/apps/admin/views/users.py:473
442 442 msgid "An error occurred during deletion of user"
443 443 msgstr ""
444 444
445 #: rhodecode/apps/admin/views/users.py:562
445 #: rhodecode/apps/admin/views/users.py:536
446 446 msgid ""
447 447 "The user participates as reviewer in {} pull request and cannot be deleted. \n"
448 448 "You can set the user to \"{}\" instead of deleting it."
449 449 msgstr ""
450 450
451 #: rhodecode/apps/admin/views/users.py:568
451 #: rhodecode/apps/admin/views/users.py:542
452 452 msgid ""
453 453 "The user participates as reviewer in {} pull requests and cannot be deleted. \n"
454 454 "You can set the user to \"{}\" instead of deleting it."
455 455 msgstr ""
456 456
457 #: rhodecode/apps/admin/views/users.py:657
457 #: rhodecode/apps/admin/views/users.py:625
458 458 msgid "User global permissions updated successfully"
459 459 msgstr ""
460 460
461 #: rhodecode/apps/admin/views/users.py:675
462 #: rhodecode/apps/user_group/views/__init__.py:479
461 #: rhodecode/apps/admin/views/users.py:643
462 #: rhodecode/apps/user_group/views/__init__.py:449
463 463 msgid "An error occurred during permissions saving"
464 464 msgstr ""
465 465
466 #: rhodecode/apps/admin/views/users.py:698
466 #: rhodecode/apps/admin/views/users.py:663
467 467 msgid "Force password change enabled for user"
468 468 msgstr ""
469 469
470 #: rhodecode/apps/admin/views/users.py:706
471 #: rhodecode/apps/admin/views/users.py:736
470 #: rhodecode/apps/admin/views/users.py:671
471 #: rhodecode/apps/admin/views/users.py:698
472 472 msgid "An error occurred during password reset for user"
473 473 msgstr ""
474 474
475 #: rhodecode/apps/admin/views/users.py:727
475 #: rhodecode/apps/admin/views/users.py:689
476 476 msgid "Force password change disabled for user"
477 477 msgstr ""
478 478
479 #: rhodecode/apps/admin/views/users.py:800
479 #: rhodecode/apps/admin/views/users.py:756
480 480 #, python-format
481 481 msgid "Linked repository group `%s` as personal"
482 482 msgstr ""
483 483
484 #: rhodecode/apps/admin/views/users.py:806
484 #: rhodecode/apps/admin/views/users.py:762
485 485 #, python-format
486 486 msgid "Created repository group `%s`"
487 487 msgstr ""
488 488
489 #: rhodecode/apps/admin/views/users.py:810
489 #: rhodecode/apps/admin/views/users.py:766
490 490 #, python-format
491 491 msgid "Repository group `%s` is already taken"
492 492 msgstr ""
493 493
494 #: rhodecode/apps/admin/views/users.py:815
494 #: rhodecode/apps/admin/views/users.py:771
495 495 msgid "An error occurred during repository group creation for user"
496 496 msgstr ""
497 497
498 #: rhodecode/apps/admin/views/users.py:838
499 #: rhodecode/apps/my_account/views/my_account.py:161
498 #: rhodecode/apps/admin/views/users.py:791
499 #: rhodecode/apps/my_account/views/my_account.py:216
500 500 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:28
501 501 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:33
502 502 msgid "Role"
503 503 msgstr ""
504 504
505 #: rhodecode/apps/admin/views/users.py:896
506 #: rhodecode/apps/my_account/views/my_account.py:217
505 #: rhodecode/apps/admin/views/users.py:844
506 #: rhodecode/apps/my_account/views/my_account.py:267
507 507 msgid "Auth token successfully created"
508 508 msgstr ""
509 509
510 #: rhodecode/apps/admin/views/users.py:925
511 #: rhodecode/apps/my_account/views/my_account.py:241
510 #: rhodecode/apps/admin/views/users.py:871
511 #: rhodecode/apps/my_account/views/my_account.py:289
512 512 msgid "Auth token successfully deleted"
513 513 msgstr ""
514 514
515 #: rhodecode/apps/admin/views/users.py:1001
516 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:117
515 #: rhodecode/apps/admin/views/users.py:939
516 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:106
517 517 msgid "Ssh Key successfully created"
518 518 msgstr ""
519 519
520 #: rhodecode/apps/admin/views/users.py:1007
521 #: rhodecode/apps/admin/views/users.py:1011
522 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:123
523 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:127
520 #: rhodecode/apps/admin/views/users.py:945
521 #: rhodecode/apps/admin/views/users.py:949
522 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:112
523 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:116
524 524 msgid "An error occurred during ssh key saving: {}"
525 525 msgstr ""
526 526
527 #: rhodecode/apps/admin/views/users.py:1045
528 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:157
527 #: rhodecode/apps/admin/views/users.py:981
528 #: rhodecode/apps/my_account/views/my_account_ssh_keys.py:144
529 529 msgid "Ssh key successfully deleted"
530 530 msgstr ""
531 531
532 #: rhodecode/apps/admin/views/users.py:1091
532 #: rhodecode/apps/admin/views/users.py:1022
533 533 #, python-format
534 534 msgid "Added new email address `%s` for user account"
535 535 msgstr ""
536 536
537 #: rhodecode/apps/admin/views/users.py:1097
537 #: rhodecode/apps/admin/views/users.py:1028
538 538 msgid "Email `{}` is already registered for another user."
539 539 msgstr ""
540 540
541 #: rhodecode/apps/admin/views/users.py:1101
541 #: rhodecode/apps/admin/views/users.py:1032
542 542 msgid "An error occurred during email saving"
543 543 msgstr ""
544 544
545 #: rhodecode/apps/admin/views/users.py:1128
545 #: rhodecode/apps/admin/views/users.py:1057
546 546 msgid "Removed email address from user account"
547 547 msgstr ""
548 548
549 #: rhodecode/apps/admin/views/users.py:1174
549 #: rhodecode/apps/admin/views/users.py:1098
550 550 #, python-format
551 551 msgid "An error occurred during ip saving:%s"
552 552 msgstr ""
553 553
554 #: rhodecode/apps/admin/views/users.py:1196
554 #: rhodecode/apps/admin/views/users.py:1120
555 555 msgid "An error occurred during ip saving"
556 556 msgstr ""
557 557
558 #: rhodecode/apps/admin/views/users.py:1200
558 #: rhodecode/apps/admin/views/users.py:1124
559 559 #, python-format
560 560 msgid "Added ips %s to user whitelist"
561 561 msgstr ""
562 562
563 #: rhodecode/apps/admin/views/users.py:1230
563 #: rhodecode/apps/admin/views/users.py:1152
564 564 msgid "Removed ip address from user whitelist"
565 565 msgstr ""
566 566
567 #: rhodecode/apps/admin/views/users.py:1295
567 #: rhodecode/apps/admin/views/users.py:1212
568 568 msgid "Groups successfully changed"
569 569 msgstr ""
570 570
571 #: rhodecode/apps/admin/views/users.py:1415
571 #: rhodecode/apps/admin/views/users.py:1315
572 572 msgid "Deleted {} cache keys"
573 573 msgstr ""
574 574
575 #: rhodecode/apps/gist/views.py:57 rhodecode/model/auth_token.py:51
575 #: rhodecode/apps/gist/views.py:56 rhodecode/model/auth_token.py:51
576 576 msgid "forever"
577 577 msgstr ""
578 578
579 #: rhodecode/apps/gist/views.py:57
580 msgid "5 minutes"
581 msgstr ""
582
579 583 #: rhodecode/apps/gist/views.py:58
580 msgid "5 minutes"
584 msgid "1 hour"
581 585 msgstr ""
582 586
583 587 #: rhodecode/apps/gist/views.py:59
584 msgid "1 hour"
588 msgid "1 day"
585 589 msgstr ""
586 590
587 591 #: rhodecode/apps/gist/views.py:60
588 msgid "1 day"
589 msgstr ""
590
591 #: rhodecode/apps/gist/views.py:61
592 592 msgid "1 month"
593 593 msgstr ""
594 594
595 #: rhodecode/apps/gist/views.py:64 rhodecode/public/js/scripts.js:48330
595 #: rhodecode/apps/gist/views.py:63 rhodecode/public/js/scripts.js:48529
596 596 #: rhodecode/public/js/scripts.min.js:1
597 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:47
597 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:48
598 598 #: rhodecode/public/js/src/rhodecode.js:634
599 599 msgid "Lifetime"
600 600 msgstr ""
601 601
602 #: rhodecode/apps/gist/views.py:65
603 msgid "Requires registered account"
604 msgstr ""
605
602 606 #: rhodecode/apps/gist/views.py:66
603 msgid "Requires registered account"
604 msgstr ""
605
606 #: rhodecode/apps/gist/views.py:67
607 607 msgid "Can be accessed by anonymous users"
608 608 msgstr ""
609 609
610 #: rhodecode/apps/gist/views.py:218
610 #: rhodecode/apps/gist/views.py:208
611 611 msgid "Error occurred during gist creation"
612 612 msgstr ""
613 613
614 #: rhodecode/apps/gist/views.py:242
614 #: rhodecode/apps/gist/views.py:230
615 615 #, python-format
616 616 msgid "Deleted gist %s"
617 617 msgstr ""
618 618
619 #: rhodecode/apps/gist/views.py:330
619 #: rhodecode/apps/gist/views.py:303
620 620 #: rhodecode/templates/admin/gists/gist_show.mako:76
621 621 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:50
622 622 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:55
623 623 #: rhodecode/templates/data_table/_dt_elements.mako:341
624 624 msgid "never"
625 625 msgstr ""
626 626
627 #: rhodecode/apps/gist/views.py:336
627 #: rhodecode/apps/gist/views.py:309
628 628 #, python-format
629 629 msgid "%(expiry)s - current value"
630 630 msgstr ""
631 631
632 #: rhodecode/apps/gist/views.py:381
632 #: rhodecode/apps/gist/views.py:351
633 633 msgid "Successfully updated gist content"
634 634 msgstr ""
635 635
636 #: rhodecode/apps/gist/views.py:386
636 #: rhodecode/apps/gist/views.py:356
637 637 msgid "Successfully updated gist data"
638 638 msgstr ""
639 639
640 #: rhodecode/apps/gist/views.py:389
640 #: rhodecode/apps/gist/views.py:359
641 641 msgid "Error occurred during update of gist {}: {}"
642 642 msgstr ""
643 643
644 #: rhodecode/apps/gist/views.py:393
644 #: rhodecode/apps/gist/views.py:363
645 645 #, python-format
646 646 msgid "Error occurred during update of gist %s"
647 647 msgstr ""
648 648
649 #: rhodecode/apps/home/views.py:453
650 #: rhodecode/apps/repository/views/repo_pull_requests.py:983
649 #: rhodecode/apps/home/views.py:442
650 #: rhodecode/apps/repository/views/repo_pull_requests.py:972
651 651 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:219
652 652 #: rhodecode/templates/admin/repos/repo_add.mako:15
653 653 #: rhodecode/templates/admin/repos/repo_add.mako:19
654 654 #: rhodecode/templates/admin/users/user_edit_advanced.mako:12
655 655 #: rhodecode/templates/base/base.mako:114
656 656 #: rhodecode/templates/base/base.mako:133
657 657 #: rhodecode/templates/base/base.mako:1191
658 658 msgid "Repositories"
659 659 msgstr ""
660 660
661 #: rhodecode/apps/home/views.py:480
661 #: rhodecode/apps/home/views.py:466
662 662 #: rhodecode/templates/admin/integrations/form.mako:17
663 663 #: rhodecode/templates/admin/integrations/list.mako:10
664 664 #: rhodecode/templates/admin/permissions/permissions_objects.mako:31
665 665 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:218
666 666 msgid "Repository Groups"
667 667 msgstr ""
668 668
669 #: rhodecode/apps/journal/views.py:133 rhodecode/apps/journal/views.py:179
669 #: rhodecode/apps/journal/views.py:129 rhodecode/apps/journal/views.py:175
670 670 msgid "public journal"
671 671 msgstr ""
672 672
673 #: rhodecode/apps/journal/views.py:137 rhodecode/apps/journal/views.py:183
673 #: rhodecode/apps/journal/views.py:133 rhodecode/apps/journal/views.py:179
674 674 msgid "journal"
675 675 msgstr ""
676 676
677 #: rhodecode/apps/login/views.py:304 rhodecode/apps/login/views.py:403
677 #: rhodecode/apps/login/views.py:290 rhodecode/apps/login/views.py:386
678 678 msgid "Bad captcha"
679 679 msgstr ""
680 680
681 #: rhodecode/apps/login/views.py:330
681 #: rhodecode/apps/login/views.py:316
682 682 msgid "You have successfully registered with RhodeCode. You can log-in now."
683 683 msgstr ""
684 684
685 #: rhodecode/apps/login/views.py:334
685 #: rhodecode/apps/login/views.py:320
686 686 msgid "Please use the {identity} button to log-in"
687 687 msgstr ""
688 688
689 #: rhodecode/apps/login/views.py:372
689 #: rhodecode/apps/login/views.py:355
690 690 msgid "If such email exists, a password reset link was sent to it."
691 691 msgstr ""
692 692
693 #: rhodecode/apps/login/views.py:385
693 #: rhodecode/apps/login/views.py:368
694 694 msgid "Password reset has been disabled."
695 695 msgstr ""
696 696
697 #: rhodecode/apps/login/views.py:474
697 #: rhodecode/apps/login/views.py:455
698 698 msgid "Given reset token is invalid"
699 699 msgstr ""
700 700
701 #: rhodecode/apps/login/views.py:482
701 #: rhodecode/apps/login/views.py:463
702 702 msgid "Your password reset was successful, a new password has been sent to your email"
703 703 msgstr ""
704 704
705 #: rhodecode/apps/my_account/views/my_account.py:136
705 #: rhodecode/apps/my_account/views/my_account.py:135
706 msgid "Your account was updated successfully"
707 msgstr ""
708
709 #: rhodecode/apps/my_account/views/my_account.py:142
710 msgid "Error occurred during update of user"
711 msgstr ""
712
713 #: rhodecode/apps/my_account/views/my_account.py:194
706 714 msgid "Error occurred during update of user password"
707 715 msgstr ""
708 716
709 #: rhodecode/apps/my_account/views/my_account.py:143
717 #: rhodecode/apps/my_account/views/my_account.py:201
710 718 msgid "Successfully updated password"
711 719 msgstr ""
712 720
713 #: rhodecode/apps/my_account/views/my_account.py:305
721 #: rhodecode/apps/my_account/views/my_account.py:347
714 722 msgid "Error occurred during adding email"
715 723 msgstr ""
716 724
717 #: rhodecode/apps/my_account/views/my_account.py:308
725 #: rhodecode/apps/my_account/views/my_account.py:350
718 726 msgid "Successfully added email"
719 727 msgstr ""
720 728
721 #: rhodecode/apps/my_account/views/my_account.py:330
729 #: rhodecode/apps/my_account/views/my_account.py:370
722 730 msgid "Email successfully deleted"
723 731 msgstr ""
724 732
725 #: rhodecode/apps/my_account/views/my_account.py:540
733 #: rhodecode/apps/my_account/views/my_account.py:566
726 734 msgid "Position {} is defined twice. Please correct this error."
727 735 msgstr ""
728 736
729 #: rhodecode/apps/my_account/views/my_account.py:553
737 #: rhodecode/apps/my_account/views/my_account.py:579
730 738 msgid "Update Bookmarks"
731 739 msgstr ""
732 740
733 #: rhodecode/apps/my_account/views/my_account.py:555
741 #: rhodecode/apps/my_account/views/my_account.py:581
734 742 msgid "Failed to update bookmarks. Make sure an unique position is used."
735 743 msgstr ""
736 744
737 #: rhodecode/apps/my_account/views/my_account.py:709
738 msgid "Your account was updated successfully"
739 msgstr ""
740
741 #: rhodecode/apps/my_account/views/my_account.py:716
742 msgid "Error occurred during update of user"
743 msgstr ""
744
745 #: rhodecode/apps/repo_group/views/repo_group_advanced.py:57
746 #: rhodecode/apps/repository/views/repo_settings_advanced.py:85
745 #: rhodecode/apps/repo_group/views/repo_group_advanced.py:53
746 #: rhodecode/apps/repository/views/repo_settings_advanced.py:82
747 747 msgid "updated commit cache"
748 748 msgstr ""
749 749
750 #: rhodecode/apps/repo_group/views/repo_group_advanced.py:105
750 #: rhodecode/apps/repo_group/views/repo_group_advanced.py:98
751 751 #, python-format
752 752 msgid "Removed repository group `%s`"
753 753 msgstr ""
754 754
755 #: rhodecode/apps/repo_group/views/repo_group_advanced.py:109
755 #: rhodecode/apps/repo_group/views/repo_group_advanced.py:102
756 756 #, python-format
757 757 msgid "Error occurred during deletion of repository group %s"
758 758 msgstr ""
759 759
760 #: rhodecode/apps/repo_group/views/repo_group_permissions.py:75
761 #: rhodecode/apps/user_group/views/__init__.py:346
760 #: rhodecode/apps/repo_group/views/repo_group_permissions.py:69
761 #: rhodecode/apps/user_group/views/__init__.py:322
762 762 msgid "Cannot change permission for yourself as admin"
763 763 msgstr ""
764 764
765 #: rhodecode/apps/repo_group/views/repo_group_permissions.py:99
765 #: rhodecode/apps/repo_group/views/repo_group_permissions.py:93
766 766 msgid "Repository Group permissions updated"
767 767 msgstr ""
768 768
769 #: rhodecode/apps/repo_group/views/repo_group_settings.py:174
769 #: rhodecode/apps/repo_group/views/repo_group_settings.py:168
770 770 msgid "Repository Group `{}` updated successfully"
771 771 msgstr ""
772 772
773 #: rhodecode/apps/repo_group/views/repo_group_settings.py:179
773 #: rhodecode/apps/repo_group/views/repo_group_settings.py:173
774 774 #, python-format
775 775 msgid "Error occurred during update of repository group %s"
776 776 msgstr ""
777 777
778 #: rhodecode/apps/repository/views/repo_caches.py:80
778 #: rhodecode/apps/repository/views/repo_caches.py:75
779 779 msgid "Cache invalidation successful"
780 780 msgstr ""
781 781
782 #: rhodecode/apps/repository/views/repo_caches.py:84
782 #: rhodecode/apps/repository/views/repo_caches.py:79
783 783 msgid "An error occurred during cache invalidation"
784 784 msgstr ""
785 785
786 786 #: rhodecode/apps/repository/views/repo_changelog.py:66
787 787 #: rhodecode/apps/repository/views/repo_compare.py:64
788 #: rhodecode/apps/repository/views/repo_pull_requests.py:832
788 #: rhodecode/apps/repository/views/repo_pull_requests.py:830
789 789 msgid "There are no commits yet"
790 790 msgstr ""
791 791
792 792 #: rhodecode/apps/repository/views/repo_changelog.py:71
793 793 #: rhodecode/apps/repository/views/repo_files.py:205
794 794 msgid "No such commit exists for this repository"
795 795 msgstr ""
796 796
797 #: rhodecode/apps/repository/views/repo_checks.py:103
797 #: rhodecode/apps/repository/views/repo_checks.py:94
798 798 #, python-format
799 799 msgid "Created repository %s from %s"
800 800 msgstr ""
801 801
802 #: rhodecode/apps/repository/views/repo_checks.py:112
802 #: rhodecode/apps/repository/views/repo_checks.py:103
803 803 #, python-format
804 804 msgid "Forked repository %s as %s"
805 805 msgstr ""
806 806
807 #: rhodecode/apps/repository/views/repo_checks.py:115
807 #: rhodecode/apps/repository/views/repo_checks.py:106
808 808 #, python-format
809 809 msgid "Created repository %s"
810 810 msgstr ""
811 811
812 #: rhodecode/apps/repository/views/repo_commits.py:113
812 #: rhodecode/apps/repository/views/repo_commits.py:112
813 813 msgid "No such commit exists. Org exception: `{}`"
814 814 msgstr ""
815 815
816 #: rhodecode/apps/repository/views/repo_commits.py:404
817 #: rhodecode/apps/repository/views/repo_pull_requests.py:1620
816 #: rhodecode/apps/repository/views/repo_commits.py:388
817 #: rhodecode/apps/repository/views/repo_pull_requests.py:1588
818 818 #, python-format
819 819 msgid "Status change %(transition_icon)s %(status)s"
820 820 msgstr ""
821 821
822 #: rhodecode/apps/repository/views/repo_commits.py:442
822 #: rhodecode/apps/repository/views/repo_commits.py:426
823 823 msgid "Changing the status of a commit associated with a closed pull request is not allowed"
824 824 msgstr ""
825 825
826 #: rhodecode/apps/repository/views/repo_commits.py:488
827 #: rhodecode/apps/repository/views/repo_pull_requests.py:1703
826 #: rhodecode/apps/repository/views/repo_commits.py:472
827 #: rhodecode/apps/repository/views/repo_pull_requests.py:1671
828 828 msgid "posted {} new {} comment"
829 829 msgstr ""
830 830
831 #: rhodecode/apps/repository/views/repo_commits.py:490
832 #: rhodecode/apps/repository/views/repo_pull_requests.py:1705
831 #: rhodecode/apps/repository/views/repo_commits.py:474
832 #: rhodecode/apps/repository/views/repo_pull_requests.py:1673
833 833 msgid "posted {} new {} comments"
834 834 msgstr ""
835 835
836 #: rhodecode/apps/repository/views/repo_compare.py:102
836 #: rhodecode/apps/repository/views/repo_compare.py:99
837 837 msgid "Select commit"
838 838 msgstr ""
839 839
840 #: rhodecode/apps/repository/views/repo_compare.py:173
840 #: rhodecode/apps/repository/views/repo_compare.py:167
841 841 msgid "Could not find the source repo: `{}`"
842 842 msgstr ""
843 843
844 #: rhodecode/apps/repository/views/repo_compare.py:181
844 #: rhodecode/apps/repository/views/repo_compare.py:175
845 845 msgid "Could not find the target repo: `{}`"
846 846 msgstr ""
847 847
848 #: rhodecode/apps/repository/views/repo_compare.py:192
848 #: rhodecode/apps/repository/views/repo_compare.py:186
849 849 msgid "The comparison of two different kinds of remote repos is not available"
850 850 msgstr ""
851 851
852 #: rhodecode/apps/repository/views/repo_compare.py:225
852 #: rhodecode/apps/repository/views/repo_compare.py:219
853 853 msgid "Could not compare repos with different large file settings"
854 854 msgstr ""
855 855
856 #: rhodecode/apps/repository/views/repo_compare.py:271
856 #: rhodecode/apps/repository/views/repo_compare.py:265
857 857 #, python-format
858 858 msgid "Repositories unrelated. Cannot compare commit %(commit1)s from repository %(repo1)s with commit %(commit2)s from repository %(repo2)s."
859 859 msgstr ""
860 860
861 #: rhodecode/apps/repository/views/repo_feed.py:68
861 #: rhodecode/apps/repository/views/repo_feed.py:66
862 862 #, python-format
863 863 msgid "Changes on %s repository"
864 864 msgstr ""
865 865
866 #: rhodecode/apps/repository/views/repo_feed.py:69
866 #: rhodecode/apps/repository/views/repo_feed.py:67
867 867 #, python-format
868 868 msgid "%s %s feed"
869 869 msgstr ""
870 870
871 871 #: rhodecode/apps/repository/views/repo_files.py:96
872 872 #, python-format
873 873 msgid "This repository has been locked by %s on %s"
874 874 msgstr ""
875 875
876 876 #: rhodecode/apps/repository/views/repo_files.py:109
877 877 msgid "Cannot modify file. Given commit `{}` is not head of a branch."
878 878 msgstr ""
879 879
880 880 #: rhodecode/apps/repository/views/repo_files.py:127
881 881 msgid "Branch `{}` changes forbidden by rule {}."
882 882 msgstr ""
883 883
884 884 #: rhodecode/apps/repository/views/repo_files.py:175
885 885 msgid "Click here to add a new file."
886 886 msgstr ""
887 887
888 888 #: rhodecode/apps/repository/views/repo_files.py:180
889 889 #, python-format
890 890 msgid "There are no files yet. %s"
891 891 msgstr ""
892 892
893 893 #: rhodecode/apps/repository/views/repo_files.py:185
894 894 msgid "No such commit exists for this repository. Commit: {}"
895 895 msgstr ""
896 896
897 #: rhodecode/apps/repository/views/repo_files.py:361
897 #: rhodecode/apps/repository/views/repo_files.py:358
898 898 msgid "Downloads disabled"
899 899 msgstr ""
900 900
901 #: rhodecode/apps/repository/views/repo_files.py:367
901 #: rhodecode/apps/repository/views/repo_files.py:364
902 902 msgid "Unknown archive type for: `{}`"
903 903 msgstr ""
904 904
905 #: rhodecode/apps/repository/views/repo_files.py:370
906 msgid "Unknown commit_id {}"
907 msgstr ""
908
905 909 #: rhodecode/apps/repository/views/repo_files.py:373
906 msgid "Unknown commit_id {}"
907 msgstr ""
908
909 #: rhodecode/apps/repository/views/repo_files.py:376
910 910 msgid "Empty repository"
911 911 msgstr ""
912 912
913 #: rhodecode/apps/repository/views/repo_files.py:381
913 #: rhodecode/apps/repository/views/repo_files.py:378
914 914 msgid "No node at path {} for this repository"
915 915 msgstr ""
916 916
917 #: rhodecode/apps/repository/views/repo_files.py:432
917 #: rhodecode/apps/repository/views/repo_files.py:429
918 918 msgid "Unknown archive type"
919 919 msgstr ""
920 920
921 #: rhodecode/apps/repository/views/repo_files.py:1027
921 #: rhodecode/apps/repository/views/repo_files.py:986
922 922 msgid "Changesets"
923 923 msgstr ""
924 924
925 #: rhodecode/apps/repository/views/repo_files.py:1048
926 #: rhodecode/apps/repository/views/repo_summary.py:264
927 #: rhodecode/model/pull_request.py:1896 rhodecode/model/scm.py:999
925 #: rhodecode/apps/repository/views/repo_files.py:1007
926 #: rhodecode/apps/repository/views/repo_summary.py:243
927 #: rhodecode/model/pull_request.py:1910 rhodecode/model/scm.py:999
928 928 #: rhodecode/templates/base/vcs_settings.mako:235
929 929 #: rhodecode/templates/summary/components.mako:10
930 930 msgid "Branches"
931 931 msgstr ""
932 932
933 #: rhodecode/apps/repository/views/repo_files.py:1052
933 #: rhodecode/apps/repository/views/repo_files.py:1011
934 934 #: rhodecode/model/scm.py:1016 rhodecode/templates/base/vcs_settings.mako:260
935 935 #: rhodecode/templates/summary/components.mako:34
936 936 msgid "Tags"
937 937 msgstr ""
938 938
939 #: rhodecode/apps/repository/views/repo_files.py:1208
940 #: rhodecode/apps/repository/views/repo_files.py:1237
939 #: rhodecode/apps/repository/views/repo_files.py:1155
940 #: rhodecode/apps/repository/views/repo_files.py:1181
941 941 msgid "Deleted file {} via RhodeCode Enterprise"
942 942 msgstr ""
943 943
944 #: rhodecode/apps/repository/views/repo_files.py:1258
944 #: rhodecode/apps/repository/views/repo_files.py:1202
945 945 msgid "Successfully deleted file `{}`"
946 946 msgstr ""
947 947
948 #: rhodecode/apps/repository/views/repo_files.py:1262
949 #: rhodecode/apps/repository/views/repo_files.py:1381
950 #: rhodecode/apps/repository/views/repo_files.py:1514
951 #: rhodecode/apps/repository/views/repo_files.py:1638
948 #: rhodecode/apps/repository/views/repo_files.py:1206
949 #: rhodecode/apps/repository/views/repo_files.py:1319
950 #: rhodecode/apps/repository/views/repo_files.py:1443
951 #: rhodecode/apps/repository/views/repo_files.py:1564
952 952 msgid "Error occurred during commit"
953 953 msgstr ""
954 954
955 #: rhodecode/apps/repository/views/repo_files.py:1295
956 #: rhodecode/apps/repository/views/repo_files.py:1327
955 #: rhodecode/apps/repository/views/repo_files.py:1236
956 #: rhodecode/apps/repository/views/repo_files.py:1265
957 957 msgid "Edited file {} via RhodeCode Enterprise"
958 958 msgstr ""
959 959
960 #: rhodecode/apps/repository/views/repo_files.py:1350
960 #: rhodecode/apps/repository/views/repo_files.py:1288
961 961 msgid "No changes detected on {}"
962 962 msgstr ""
963 963
964 #: rhodecode/apps/repository/views/repo_files.py:1374
964 #: rhodecode/apps/repository/views/repo_files.py:1312
965 965 msgid "Successfully committed changes to file `{}`"
966 966 msgstr ""
967 967
968 #: rhodecode/apps/repository/views/repo_files.py:1416
969 #: rhodecode/apps/repository/views/repo_files.py:1458
968 #: rhodecode/apps/repository/views/repo_files.py:1348
969 #: rhodecode/apps/repository/views/repo_files.py:1387
970 970 msgid "Added file via RhodeCode Enterprise"
971 971 msgstr ""
972 972
973 #: rhodecode/apps/repository/views/repo_files.py:1474
973 #: rhodecode/apps/repository/views/repo_files.py:1403
974 974 msgid "No filename specified"
975 975 msgstr ""
976 976
977 #: rhodecode/apps/repository/views/repo_files.py:1499
977 #: rhodecode/apps/repository/views/repo_files.py:1428
978 978 msgid "Successfully committed new file `{}`"
979 979 msgstr ""
980 980
981 #: rhodecode/apps/repository/views/repo_files.py:1507
982 #: rhodecode/apps/repository/views/repo_files.py:1620
981 #: rhodecode/apps/repository/views/repo_files.py:1436
982 #: rhodecode/apps/repository/views/repo_files.py:1546
983 983 msgid "The location specified must be a relative path and must not contain .. in the path"
984 984 msgstr ""
985 985
986 #: rhodecode/apps/repository/views/repo_files.py:1565
986 #: rhodecode/apps/repository/views/repo_files.py:1491
987 987 msgid "Uploaded file via RhodeCode Enterprise"
988 988 msgstr ""
989 989
990 #: rhodecode/apps/repository/views/repo_files.py:1609
990 #: rhodecode/apps/repository/views/repo_files.py:1535
991 991 msgid "Successfully committed {} new files"
992 992 msgstr ""
993 993
994 #: rhodecode/apps/repository/views/repo_files.py:1611
994 #: rhodecode/apps/repository/views/repo_files.py:1537
995 995 msgid "Successfully committed 1 new file"
996 996 msgstr ""
997 997
998 #: rhodecode/apps/repository/views/repo_forks.py:146
998 #: rhodecode/apps/repository/views/repo_forks.py:140
999 999 msgid "Compare fork"
1000 1000 msgstr ""
1001 1001
1002 #: rhodecode/apps/repository/views/repo_forks.py:251
1002 #: rhodecode/apps/repository/views/repo_forks.py:239
1003 1003 #, python-format
1004 1004 msgid "An error occurred during repository forking %s"
1005 1005 msgstr ""
1006 1006
1007 #: rhodecode/apps/repository/views/repo_permissions.py:57
1007 #: rhodecode/apps/repository/views/repo_permissions.py:53
1008 1008 msgid "Explicitly add user or user group with write or higher permission to modify their branch permissions."
1009 1009 msgstr ""
1010 1010
1011 #: rhodecode/apps/repository/views/repo_permissions.py:92
1011 #: rhodecode/apps/repository/views/repo_permissions.py:85
1012 1012 msgid "Repository access permissions updated"
1013 1013 msgstr ""
1014 1014
1015 #: rhodecode/apps/repository/views/repo_permissions.py:126
1015 #: rhodecode/apps/repository/views/repo_permissions.py:116
1016 1016 msgid "Repository `{}` private mode set successfully"
1017 1017 msgstr ""
1018 1018
1019 #: rhodecode/apps/repository/views/repo_permissions.py:134
1020 #: rhodecode/apps/repository/views/repo_settings.py:176
1019 #: rhodecode/apps/repository/views/repo_permissions.py:124
1020 #: rhodecode/apps/repository/views/repo_settings.py:169
1021 1021 msgid "Error occurred during update of repository {}"
1022 1022 msgstr ""
1023 1023
1024 #: rhodecode/apps/repository/views/repo_pull_requests.py:331
1024 #: rhodecode/apps/repository/views/repo_pull_requests.py:326
1025 1025 msgid "Pull Request state was force changed to `{}`"
1026 1026 msgstr ""
1027 1027
1028 #: rhodecode/apps/repository/views/repo_pull_requests.py:862
1028 #: rhodecode/apps/repository/views/repo_pull_requests.py:857
1029 1029 msgid "Commit does not exist"
1030 1030 msgstr ""
1031 1031
1032 #: rhodecode/apps/repository/views/repo_pull_requests.py:1142
1032 #: rhodecode/apps/repository/views/repo_pull_requests.py:1119
1033 1033 msgid "Error creating pull request: {}"
1034 1034 msgstr ""
1035 1035
1036 #: rhodecode/apps/repository/views/repo_pull_requests.py:1162
1036 #: rhodecode/apps/repository/views/repo_pull_requests.py:1139
1037 1037 msgid "source_repo or target repo not found"
1038 1038 msgstr ""
1039 1039
1040 #: rhodecode/apps/repository/views/repo_pull_requests.py:1173
1040 #: rhodecode/apps/repository/views/repo_pull_requests.py:1150
1041 1041 msgid "Not Enough permissions to source repo `{}`."
1042 1042 msgstr ""
1043 1043
1044 #: rhodecode/apps/repository/views/repo_pull_requests.py:1188
1044 #: rhodecode/apps/repository/views/repo_pull_requests.py:1165
1045 1045 msgid "Not Enough permissions to target repo `{}`."
1046 1046 msgstr ""
1047 1047
1048 #: rhodecode/apps/repository/views/repo_pull_requests.py:1258
1048 #: rhodecode/apps/repository/views/repo_pull_requests.py:1235
1049 1049 msgid "Successfully opened new pull request"
1050 1050 msgstr ""
1051 1051
1052 #: rhodecode/apps/repository/views/repo_pull_requests.py:1261
1052 #: rhodecode/apps/repository/views/repo_pull_requests.py:1238
1053 1053 msgid "Error occurred during creation of this pull request."
1054 1054 msgstr ""
1055 1055
1056 #: rhodecode/apps/repository/views/repo_pull_requests.py:1293
1057 #: rhodecode/apps/repository/views/repo_pull_requests.py:1362
1056 #: rhodecode/apps/repository/views/repo_pull_requests.py:1267
1057 #: rhodecode/apps/repository/views/repo_pull_requests.py:1336
1058 1058 msgid "Cannot update closed pull requests."
1059 1059 msgstr ""
1060 1060
1061 #: rhodecode/apps/repository/views/repo_pull_requests.py:1325
1061 #: rhodecode/apps/repository/views/repo_pull_requests.py:1299
1062 1062 msgid "Cannot update pull requests commits in state other than `{}`. Current state is: `{}`"
1063 1063 msgstr ""
1064 1064
1065 #: rhodecode/apps/repository/views/repo_pull_requests.py:1368
1065 #: rhodecode/apps/repository/views/repo_pull_requests.py:1342
1066 1066 msgid "Pull request title & description updated."
1067 1067 msgstr ""
1068 1068
1069 #: rhodecode/apps/repository/views/repo_pull_requests.py:1390
1069 #: rhodecode/apps/repository/views/repo_pull_requests.py:1364
1070 1070 msgid "Pull request updated to \"{source_commit_id}\" with {count_added} added, {count_removed} removed commits. Source of changes: {change_source}."
1071 1071 msgstr ""
1072 1072
1073 #: rhodecode/apps/repository/views/repo_pull_requests.py:1430
1073 #: rhodecode/apps/repository/views/repo_pull_requests.py:1404
1074 1074 msgid "Pull request reviewers updated."
1075 1075 msgstr ""
1076 1076
1077 #: rhodecode/apps/repository/views/repo_pull_requests.py:1454
1077 #: rhodecode/apps/repository/views/repo_pull_requests.py:1428
1078 1078 msgid "Pull request observers updated."
1079 1079 msgstr ""
1080 1080
1081 #: rhodecode/apps/repository/views/repo_pull_requests.py:1481
1081 #: rhodecode/apps/repository/views/repo_pull_requests.py:1452
1082 1082 msgid "Cannot merge pull requests in state other than `{}`. Current state is: `{}`"
1083 1083 msgstr ""
1084 1084
1085 #: rhodecode/apps/repository/views/repo_pull_requests.py:1527
1085 #: rhodecode/apps/repository/views/repo_pull_requests.py:1498
1086 1086 msgid "Pull request was successfully merged and closed."
1087 1087 msgstr ""
1088 1088
1089 #: rhodecode/apps/repository/views/repo_pull_requests.py:1558
1089 #: rhodecode/apps/repository/views/repo_pull_requests.py:1526
1090 1090 msgid "Successfully deleted pull request"
1091 1091 msgstr ""
1092 1092
1093 #: rhodecode/apps/repository/views/repo_settings.py:172
1093 #: rhodecode/apps/repository/views/repo_settings.py:165
1094 1094 msgid "Repository `{}` updated successfully"
1095 1095 msgstr ""
1096 1096
1097 #: rhodecode/apps/repository/views/repo_settings.py:209
1097 #: rhodecode/apps/repository/views/repo_settings.py:199
1098 1098 msgid "Unlocked"
1099 1099 msgstr ""
1100 1100
1101 #: rhodecode/apps/repository/views/repo_settings.py:214
1101 #: rhodecode/apps/repository/views/repo_settings.py:204
1102 1102 msgid "Locked"
1103 1103 msgstr ""
1104 1104
1105 #: rhodecode/apps/repository/views/repo_settings.py:216
1105 #: rhodecode/apps/repository/views/repo_settings.py:206
1106 1106 #, python-format
1107 1107 msgid "Repository has been %s"
1108 1108 msgstr ""
1109 1109
1110 #: rhodecode/apps/repository/views/repo_settings.py:220
1111 #: rhodecode/apps/repository/views/repo_settings_advanced.py:305
1110 #: rhodecode/apps/repository/views/repo_settings.py:210
1111 #: rhodecode/apps/repository/views/repo_settings_advanced.py:287
1112 1112 msgid "An error occurred during unlocking"
1113 1113 msgstr ""
1114 1114
1115 #: rhodecode/apps/repository/views/repo_settings.py:264
1115 #: rhodecode/apps/repository/views/repo_settings.py:248
1116 1116 msgid "An error occurred during deletion of repository stats"
1117 1117 msgstr ""
1118 1118
1119 #: rhodecode/apps/repository/views/repo_settings_advanced.py:114
1119 #: rhodecode/apps/repository/views/repo_settings_advanced.py:108
1120 1120 #, python-format
1121 1121 msgid "Archived repository `%s`"
1122 1122 msgstr ""
1123 1123
1124 #: rhodecode/apps/repository/views/repo_settings_advanced.py:119
1124 #: rhodecode/apps/repository/views/repo_settings_advanced.py:113
1125 1125 #, python-format
1126 1126 msgid "An error occurred during archiving of `%s`"
1127 1127 msgstr ""
1128 1128
1129 #: rhodecode/apps/repository/views/repo_settings_advanced.py:157
1129 #: rhodecode/apps/repository/views/repo_settings_advanced.py:148
1130 1130 #, python-format
1131 1131 msgid "Detached %s forks"
1132 1132 msgstr ""
1133 1133
1134 #: rhodecode/apps/repository/views/repo_settings_advanced.py:150
1135 #, python-format
1136 msgid "Deleted %s forks"
1137 msgstr ""
1138
1134 1139 #: rhodecode/apps/repository/views/repo_settings_advanced.py:159
1135 1140 #, python-format
1136 msgid "Deleted %s forks"
1137 msgstr ""
1138
1139 #: rhodecode/apps/repository/views/repo_settings_advanced.py:168
1140 #, python-format
1141 1141 msgid "Deleted repository `%s`"
1142 1142 msgstr ""
1143 1143
1144 #: rhodecode/apps/repository/views/repo_settings_advanced.py:175
1144 #: rhodecode/apps/repository/views/repo_settings_advanced.py:166
1145 1145 msgid "detach or delete"
1146 1146 msgstr ""
1147 1147
1148 #: rhodecode/apps/repository/views/repo_settings_advanced.py:176
1148 #: rhodecode/apps/repository/views/repo_settings_advanced.py:167
1149 1149 msgid "Cannot delete `{repo}` it still contains attached forks. Try using {delete_or_detach} option."
1150 1150 msgstr ""
1151 1151
1152 #: rhodecode/apps/repository/views/repo_settings_advanced.py:182
1153 msgid "Cannot delete `{repo}` it still contains {num} attached pull requests. Consider archiving the repository instead."
1154 msgstr ""
1155
1152 1156 #: rhodecode/apps/repository/views/repo_settings_advanced.py:191
1153 msgid "Cannot delete `{repo}` it still contains {num} attached pull requests. Consider archiving the repository instead."
1154 msgstr ""
1155
1156 #: rhodecode/apps/repository/views/repo_settings_advanced.py:200
1157 1157 #, python-format
1158 1158 msgid "An error occurred during deletion of `%s`"
1159 1159 msgstr ""
1160 1160
1161 #: rhodecode/apps/repository/views/repo_settings_advanced.py:225
1161 #: rhodecode/apps/repository/views/repo_settings_advanced.py:213
1162 1162 msgid "Updated repository visibility in public journal"
1163 1163 msgstr ""
1164 1164
1165 #: rhodecode/apps/repository/views/repo_settings_advanced.py:229
1165 #: rhodecode/apps/repository/views/repo_settings_advanced.py:217
1166 1166 msgid "An error occurred during setting this repository in public journal"
1167 1167 msgstr ""
1168 1168
1169 #: rhodecode/apps/repository/views/repo_settings_advanced.py:265
1169 #: rhodecode/apps/repository/views/repo_settings_advanced.py:250
1170 1170 msgid "Nothing"
1171 1171 msgstr ""
1172 1172
1173 #: rhodecode/apps/repository/views/repo_settings_advanced.py:268
1173 #: rhodecode/apps/repository/views/repo_settings_advanced.py:253
1174 1174 #, python-format
1175 1175 msgid "Marked repo %s as fork of %s"
1176 1176 msgstr ""
1177 1177
1178 #: rhodecode/apps/repository/views/repo_settings_advanced.py:275
1178 #: rhodecode/apps/repository/views/repo_settings_advanced.py:260
1179 1179 msgid "An error occurred during this operation"
1180 1180 msgstr ""
1181 1181
1182 #: rhodecode/apps/repository/views/repo_settings_advanced.py:299
1182 #: rhodecode/apps/repository/views/repo_settings_advanced.py:281
1183 1183 msgid "Locked repository"
1184 1184 msgstr ""
1185 1185
1186 #: rhodecode/apps/repository/views/repo_settings_advanced.py:302
1186 #: rhodecode/apps/repository/views/repo_settings_advanced.py:284
1187 1187 msgid "Unlocked repository"
1188 1188 msgstr ""
1189 1189
1190 #: rhodecode/apps/repository/views/repo_settings_advanced.py:322
1190 #: rhodecode/apps/repository/views/repo_settings_advanced.py:301
1191 1191 msgid "installed updated hooks into this repository"
1192 1192 msgstr ""
1193 1193
1194 #: rhodecode/apps/repository/views/repo_settings_fields.py:86
1194 #: rhodecode/apps/repository/views/repo_settings_fields.py:79
1195 1195 msgid "An error occurred during creation of field"
1196 1196 msgstr ""
1197 1197
1198 #: rhodecode/apps/repository/views/repo_settings_fields.py:108
1198 #: rhodecode/apps/repository/views/repo_settings_fields.py:98
1199 1199 msgid "An error occurred during removal of field"
1200 1200 msgstr ""
1201 1201
1202 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:86
1202 #: rhodecode/apps/repository/views/repo_settings_issue_trackers.py:77
1203 1203 msgid "Error occurred during deleting issue tracker entry"
1204 1204 msgstr ""
1205 1205
1206 #: rhodecode/apps/repository/views/repo_settings_remote.py:64
1206 #: rhodecode/apps/repository/views/repo_settings_remote.py:58
1207 1207 msgid "Pulled from remote location"
1208 1208 msgstr ""
1209 1209
1210 #: rhodecode/apps/repository/views/repo_settings_remote.py:67
1210 #: rhodecode/apps/repository/views/repo_settings_remote.py:61
1211 1211 msgid "An error occurred during pull from remote location"
1212 1212 msgstr ""
1213 1213
1214 #: rhodecode/apps/repository/views/repo_settings_vcs.py:147
1214 #: rhodecode/apps/repository/views/repo_settings_vcs.py:139
1215 1215 msgid "Error occurred during updating repository VCS settings"
1216 1216 msgstr ""
1217 1217
1218 #: rhodecode/apps/repository/views/repo_summary.py:240
1218 #: rhodecode/apps/repository/views/repo_summary.py:222
1219 1219 #: rhodecode/templates/admin/permissions/permissions.mako:42
1220 1220 #: rhodecode/templates/summary/components.mako:8
1221 1221 msgid "Branch"
1222 1222 msgstr ""
1223 1223
1224 #: rhodecode/apps/repository/views/repo_summary.py:241
1224 #: rhodecode/apps/repository/views/repo_summary.py:223
1225 1225 #: rhodecode/templates/summary/components.mako:32
1226 1226 msgid "Tag"
1227 1227 msgstr ""
1228 1228
1229 #: rhodecode/apps/repository/views/repo_summary.py:242
1229 #: rhodecode/apps/repository/views/repo_summary.py:224
1230 1230 #: rhodecode/templates/summary/components.mako:44
1231 1231 msgid "Bookmark"
1232 1232 msgstr ""
1233 1233
1234 #: rhodecode/apps/repository/views/repo_summary.py:265
1234 #: rhodecode/apps/repository/views/repo_summary.py:244
1235 1235 msgid "Closed branches"
1236 1236 msgstr ""
1237 1237
1238 1238 #: rhodecode/apps/ssh_support/events.py:29
1239 1239 msgid "RhodeCode SSH Key files changed."
1240 1240 msgstr ""
1241 1241
1242 1242 #: rhodecode/apps/svn_support/events.py:30
1243 1243 msgid "Configuration for Apache mad_dav_svn changed."
1244 1244 msgstr ""
1245 1245
1246 #: rhodecode/apps/user_group/views/__init__.py:188
1246 #: rhodecode/apps/user_group/views/__init__.py:176
1247 1247 #, python-format
1248 1248 msgid "Updated user group %s"
1249 1249 msgstr ""
1250 1250
1251 #: rhodecode/apps/user_group/views/__init__.py:224
1251 #: rhodecode/apps/user_group/views/__init__.py:212
1252 1252 #, python-format
1253 1253 msgid "Error occurred during update of user group %s"
1254 1254 msgstr ""
1255 1255
1256 #: rhodecode/apps/user_group/views/__init__.py:250
1256 #: rhodecode/apps/user_group/views/__init__.py:235
1257 1257 msgid "Successfully deleted user group"
1258 1258 msgstr ""
1259 1259
1260 #: rhodecode/apps/user_group/views/__init__.py:255
1260 #: rhodecode/apps/user_group/views/__init__.py:240
1261 1261 msgid "An error occurred during deletion of user group"
1262 1262 msgstr ""
1263 1263
1264 #: rhodecode/apps/user_group/views/__init__.py:359
1264 #: rhodecode/apps/user_group/views/__init__.py:335
1265 1265 msgid "Target group cannot be the same"
1266 1266 msgstr ""
1267 1267
1268 #: rhodecode/apps/user_group/views/__init__.py:374
1268 #: rhodecode/apps/user_group/views/__init__.py:350
1269 1269 msgid "User Group permissions updated"
1270 1270 msgstr ""
1271 1271
1272 #: rhodecode/apps/user_group/views/__init__.py:459
1272 #: rhodecode/apps/user_group/views/__init__.py:429
1273 1273 msgid "User Group global permissions updated successfully"
1274 1274 msgstr ""
1275 1275
1276 #: rhodecode/apps/user_group/views/__init__.py:541
1276 #: rhodecode/apps/user_group/views/__init__.py:505
1277 1277 msgid "User Group synchronization updated successfully"
1278 1278 msgstr ""
1279 1279
1280 #: rhodecode/apps/user_group/views/__init__.py:545
1280 #: rhodecode/apps/user_group/views/__init__.py:509
1281 1281 msgid "An error occurred during synchronization update"
1282 1282 msgstr ""
1283 1283
1284 1284 #: rhodecode/authentication/routes.py:61
1285 1285 #: rhodecode/templates/admin/auth/plugin_settings.mako:14
1286 1286 msgid "Authentication Plugins"
1287 1287 msgstr ""
1288 1288
1289 1289 #: rhodecode/authentication/schema.py:36
1290 1290 msgid "Enable or disable this authentication plugin."
1291 1291 msgstr ""
1292 1292
1293 1293 #: rhodecode/authentication/schema.py:38 rhodecode/integrations/schema.py:32
1294 1294 #: rhodecode/model/permission.py:110 rhodecode/model/permission.py:114
1295 1295 #: rhodecode/model/permission.py:118 rhodecode/model/permission.py:122
1296 1296 #: rhodecode/model/permission.py:126 rhodecode/model/permission.py:130
1297 1297 #: rhodecode/model/validation_schema/schemas/integration_schema.py:195
1298 1298 #: rhodecode/templates/admin/auth/auth_settings.mako:64
1299 1299 #: rhodecode/templates/admin/integrations/list.mako:71
1300 1300 #: rhodecode/templates/admin/my_account/my_account_notifications.mako:23
1301 1301 msgid "Enabled"
1302 1302 msgstr ""
1303 1303
1304 1304 #: rhodecode/authentication/schema.py:44
1305 1305 msgid ""
1306 1306 "Amount of seconds to cache the authentication and permissions check response call for this plugin. \n"
1307 1307 "Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled)."
1308 1308 msgstr ""
1309 1309
1310 1310 #: rhodecode/authentication/schema.py:49
1311 1311 msgid "Auth Cache TTL"
1312 1312 msgstr ""
1313 1313
1314 1314 #: rhodecode/authentication/views.py:92
1315 1315 msgid "Errors exist when saving plugin settings. Please check the form inputs."
1316 1316 msgstr ""
1317 1317
1318 1318 #: rhodecode/authentication/views.py:105 rhodecode/authentication/views.py:164
1319 1319 msgid "Auth settings updated successfully."
1320 1320 msgstr ""
1321 1321
1322 1322 #: rhodecode/authentication/views.py:167
1323 1323 msgid "Errors exist when saving plugin setting. Please check the form inputs."
1324 1324 msgstr ""
1325 1325
1326 1326 #: rhodecode/authentication/views.py:175
1327 1327 msgid "Error occurred during update of auth settings."
1328 1328 msgstr ""
1329 1329
1330 1330 #: rhodecode/authentication/plugins/auth_crowd.py:60
1331 1331 msgid "The FQDN or IP of the Atlassian CROWD Server"
1332 1332 msgstr ""
1333 1333
1334 1334 #: rhodecode/authentication/plugins/auth_crowd.py:62
1335 1335 msgid "Host"
1336 1336 msgstr ""
1337 1337
1338 1338 #: rhodecode/authentication/plugins/auth_crowd.py:67
1339 1339 msgid "The Port in use by the Atlassian CROWD Server"
1340 1340 msgstr ""
1341 1341
1342 1342 #: rhodecode/authentication/plugins/auth_crowd.py:69
1343 1343 #: rhodecode/authentication/plugins/auth_ldap.py:270
1344 1344 msgid "Port"
1345 1345 msgstr ""
1346 1346
1347 1347 #: rhodecode/authentication/plugins/auth_crowd.py:75
1348 1348 msgid "The Application Name to authenticate to CROWD"
1349 1349 msgstr ""
1350 1350
1351 1351 #: rhodecode/authentication/plugins/auth_crowd.py:77
1352 1352 msgid "Application Name"
1353 1353 msgstr ""
1354 1354
1355 1355 #: rhodecode/authentication/plugins/auth_crowd.py:82
1356 1356 msgid "The password to authenticate to CROWD"
1357 1357 msgstr ""
1358 1358
1359 1359 #: rhodecode/authentication/plugins/auth_crowd.py:84
1360 1360 msgid "Application Password"
1361 1361 msgstr ""
1362 1362
1363 1363 #: rhodecode/authentication/plugins/auth_crowd.py:89
1364 1364 msgid "A comma separated list of group names that identify users as RhodeCode Administrators"
1365 1365 msgstr ""
1366 1366
1367 1367 #: rhodecode/authentication/plugins/auth_crowd.py:93
1368 1368 msgid "Admin Groups"
1369 1369 msgstr ""
1370 1370
1371 1371 #: rhodecode/authentication/plugins/auth_crowd.py:217
1372 1372 msgid "CROWD"
1373 1373 msgstr ""
1374 1374
1375 1375 #: rhodecode/authentication/plugins/auth_headers.py:54
1376 1376 msgid "Header to extract the user from"
1377 1377 msgstr ""
1378 1378
1379 1379 #: rhodecode/authentication/plugins/auth_headers.py:56
1380 1380 msgid "Header"
1381 1381 msgstr ""
1382 1382
1383 1383 #: rhodecode/authentication/plugins/auth_headers.py:61
1384 1384 msgid "Header to extract the user from when main one fails"
1385 1385 msgstr ""
1386 1386
1387 1387 #: rhodecode/authentication/plugins/auth_headers.py:63
1388 1388 msgid "Fallback header"
1389 1389 msgstr ""
1390 1390
1391 1391 #: rhodecode/authentication/plugins/auth_headers.py:68
1392 1392 msgid "Perform cleaning of user, if passed user has @ in username then first part before @ is taken. If there's \\ in the username only the part after \\ is taken"
1393 1393 msgstr ""
1394 1394
1395 1395 #: rhodecode/authentication/plugins/auth_headers.py:73
1396 1396 msgid "Clean username"
1397 1397 msgstr ""
1398 1398
1399 1399 #: rhodecode/authentication/plugins/auth_headers.py:99
1400 1400 msgid "Headers"
1401 1401 msgstr ""
1402 1402
1403 1403 #: rhodecode/authentication/plugins/auth_jasig_cas.py:62
1404 1404 msgid "The url of the Jasig CAS REST service"
1405 1405 msgstr ""
1406 1406
1407 1407 #: rhodecode/authentication/plugins/auth_jasig_cas.py:64
1408 1408 msgid "URL"
1409 1409 msgstr ""
1410 1410
1411 1411 #: rhodecode/authentication/plugins/auth_jasig_cas.py:93
1412 1412 msgid "Jasig-CAS"
1413 1413 msgstr ""
1414 1414
1415 1415 #: rhodecode/authentication/plugins/auth_ldap.py:258
1416 1416 msgid ""
1417 1417 "Host[s] of the LDAP Server \n"
1418 1418 "(e.g., 192.168.2.154, or ldap-server.domain.com.\n"
1419 1419 " Multiple servers can be specified using commas"
1420 1420 msgstr ""
1421 1421
1422 1422 #: rhodecode/authentication/plugins/auth_ldap.py:262
1423 1423 msgid "LDAP Host"
1424 1424 msgstr ""
1425 1425
1426 1426 #: rhodecode/authentication/plugins/auth_ldap.py:267
1427 1427 msgid "Custom port that the LDAP server is listening on. Default value is: 389, use 636 for LDAPS (SSL)"
1428 1428 msgstr ""
1429 1429
1430 1430 #: rhodecode/authentication/plugins/auth_ldap.py:277
1431 1431 msgid "Timeout for LDAP connection"
1432 1432 msgstr ""
1433 1433
1434 1434 #: rhodecode/authentication/plugins/auth_ldap.py:279
1435 1435 msgid "Connection timeout"
1436 1436 msgstr ""
1437 1437
1438 1438 #: rhodecode/authentication/plugins/auth_ldap.py:286
1439 1439 msgid ""
1440 1440 "Optional user DN/account to connect to LDAP if authentication is required. \n"
1441 1441 "e.g., cn=admin,dc=mydomain,dc=com, or uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com"
1442 1442 msgstr ""
1443 1443
1444 1444 #: rhodecode/authentication/plugins/auth_ldap.py:291
1445 1445 msgid "Bind account"
1446 1446 msgstr ""
1447 1447
1448 1448 #: rhodecode/authentication/plugins/auth_ldap.py:296
1449 1449 msgid "Password to authenticate for given user DN."
1450 1450 msgstr ""
1451 1451
1452 1452 #: rhodecode/authentication/plugins/auth_ldap.py:299
1453 1453 msgid "Bind account password"
1454 1454 msgstr ""
1455 1455
1456 1456 #: rhodecode/authentication/plugins/auth_ldap.py:304
1457 1457 msgid "TLS Type"
1458 1458 msgstr ""
1459 1459
1460 1460 #: rhodecode/authentication/plugins/auth_ldap.py:305
1461 1461 msgid "Connection Security"
1462 1462 msgstr ""
1463 1463
1464 1464 #: rhodecode/authentication/plugins/auth_ldap.py:311
1465 1465 msgid ""
1466 1466 "Require Cert over TLS?. Self-signed and custom certificates can be used when\n"
1467 1467 " `RhodeCode Certificate` found in admin > settings > system info page is extended."
1468 1468 msgstr ""
1469 1469
1470 1470 #: rhodecode/authentication/plugins/auth_ldap.py:314
1471 1471 msgid "Certificate Checks"
1472 1472 msgstr ""
1473 1473
1474 1474 #: rhodecode/authentication/plugins/auth_ldap.py:320
1475 1475 msgid ""
1476 1476 "This specifies the PEM-format file path containing certificates for use in TLS connection.\n"
1477 1477 "If not specified `TLS Cert dir` will be used"
1478 1478 msgstr ""
1479 1479
1480 1480 #: rhodecode/authentication/plugins/auth_ldap.py:323
1481 1481 msgid "TLS Cert file"
1482 1482 msgstr ""
1483 1483
1484 1484 #: rhodecode/authentication/plugins/auth_ldap.py:329
1485 1485 msgid "This specifies the path of a directory that contains individual CA certificates in separate files."
1486 1486 msgstr ""
1487 1487
1488 1488 #: rhodecode/authentication/plugins/auth_ldap.py:331
1489 1489 msgid "TLS Cert dir"
1490 1490 msgstr ""
1491 1491
1492 1492 #: rhodecode/authentication/plugins/auth_ldap.py:336
1493 1493 msgid ""
1494 1494 "Base DN to search. Dynamic bind is supported. Add `$login` marker in it to be replaced with current user username \n"
1495 1495 "(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)"
1496 1496 msgstr ""
1497 1497
1498 1498 #: rhodecode/authentication/plugins/auth_ldap.py:341
1499 1499 msgid "Base DN"
1500 1500 msgstr ""
1501 1501
1502 1502 #: rhodecode/authentication/plugins/auth_ldap.py:346
1503 1503 msgid ""
1504 1504 "Filter to narrow results \n"
1505 1505 "(e.g., (&(objectCategory=Person)(objectClass=user)), or \n"
1506 1506 "(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))"
1507 1507 msgstr ""
1508 1508
1509 1509 #: rhodecode/authentication/plugins/auth_ldap.py:351
1510 1510 msgid "LDAP Search Filter"
1511 1511 msgstr ""
1512 1512
1513 1513 #: rhodecode/authentication/plugins/auth_ldap.py:357
1514 1514 msgid "How deep to search LDAP. If unsure set to SUBTREE"
1515 1515 msgstr ""
1516 1516
1517 1517 #: rhodecode/authentication/plugins/auth_ldap.py:358
1518 1518 msgid "LDAP Search Scope"
1519 1519 msgstr ""
1520 1520
1521 1521 #: rhodecode/authentication/plugins/auth_ldap.py:364
1522 1522 msgid "LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)"
1523 1523 msgstr ""
1524 1524
1525 1525 #: rhodecode/authentication/plugins/auth_ldap.py:366
1526 1526 msgid "Login Attribute"
1527 1527 msgstr ""
1528 1528
1529 1529 #: rhodecode/authentication/plugins/auth_ldap.py:367
1530 1530 msgid "The LDAP Login attribute of the CN must be specified"
1531 1531 msgstr ""
1532 1532
1533 1533 #: rhodecode/authentication/plugins/auth_ldap.py:372
1534 1534 msgid ""
1535 1535 "LDAP Attribute to map to email address (e.g., mail).\n"
1536 1536 "Emails are a crucial part of RhodeCode. \n"
1537 1537 "If possible add a valid email attribute to ldap users."
1538 1538 msgstr ""
1539 1539
1540 1540 #: rhodecode/authentication/plugins/auth_ldap.py:377
1541 1541 msgid "Email Attribute"
1542 1542 msgstr ""
1543 1543
1544 1544 #: rhodecode/authentication/plugins/auth_ldap.py:382
1545 1545 msgid "LDAP Attribute to map to first name (e.g., givenName)"
1546 1546 msgstr ""
1547 1547
1548 1548 #: rhodecode/authentication/plugins/auth_ldap.py:385
1549 1549 msgid "First Name Attribute"
1550 1550 msgstr ""
1551 1551
1552 1552 #: rhodecode/authentication/plugins/auth_ldap.py:390
1553 1553 msgid "LDAP Attribute to map to last name (e.g., sn)"
1554 1554 msgstr ""
1555 1555
1556 1556 #: rhodecode/authentication/plugins/auth_ldap.py:393
1557 1557 msgid "Last Name Attribute"
1558 1558 msgstr ""
1559 1559
1560 1560 #: rhodecode/authentication/plugins/auth_ldap.py:425
1561 1561 msgid "LDAP"
1562 1562 msgstr ""
1563 1563
1564 1564 #: rhodecode/authentication/plugins/auth_pam.py:60
1565 1565 msgid "PAM service name to use for authentication."
1566 1566 msgstr ""
1567 1567
1568 1568 #: rhodecode/authentication/plugins/auth_pam.py:62
1569 1569 msgid "PAM service name"
1570 1570 msgstr ""
1571 1571
1572 1572 #: rhodecode/authentication/plugins/auth_pam.py:67
1573 1573 msgid "Regular expression for extracting user name/email etc. from Unix userinfo."
1574 1574 msgstr ""
1575 1575
1576 1576 #: rhodecode/authentication/plugins/auth_pam.py:70
1577 1577 msgid "Gecos Regex"
1578 1578 msgstr ""
1579 1579
1580 1580 #: rhodecode/authentication/plugins/auth_pam.py:99
1581 1581 msgid "PAM"
1582 1582 msgstr ""
1583 1583
1584 1584 #: rhodecode/authentication/plugins/auth_rhodecode.py:79
1585 1585 msgid "RhodeCode Internal"
1586 1586 msgstr ""
1587 1587
1588 1588 #: rhodecode/authentication/plugins/auth_rhodecode.py:200
1589 1589 msgid "Allowed user types for authentication using this plugin."
1590 1590 msgstr ""
1591 1591
1592 1592 #: rhodecode/authentication/plugins/auth_rhodecode.py:201
1593 1593 msgid "User restriction"
1594 1594 msgstr ""
1595 1595
1596 1596 #: rhodecode/authentication/plugins/auth_rhodecode.py:209
1597 1597 msgid "Allowed protocols for authentication using this plugin. VCS means GIT/HG/SVN. HTTP is web based login."
1598 1598 msgstr ""
1599 1599
1600 1600 #: rhodecode/authentication/plugins/auth_rhodecode.py:211
1601 1601 #: rhodecode/authentication/plugins/auth_token.py:173
1602 1602 msgid "Scope restriction"
1603 1603 msgstr ""
1604 1604
1605 1605 #: rhodecode/authentication/plugins/auth_token.py:77
1606 1606 msgid "Rhodecode Token"
1607 1607 msgstr ""
1608 1608
1609 1609 #: rhodecode/authentication/plugins/auth_token.py:172
1610 1610 msgid "Choose operation scope restriction when authenticating."
1611 1611 msgstr ""
1612 1612
1613 1613 #: rhodecode/events/pullrequest.py:78
1614 1614 msgid "pullrequest created"
1615 1615 msgstr ""
1616 1616
1617 1617 #: rhodecode/events/pullrequest.py:79
1618 1618 msgid "Event triggered after pull request was created"
1619 1619 msgstr ""
1620 1620
1621 1621 #: rhodecode/events/pullrequest.py:88
1622 1622 msgid "pullrequest closed"
1623 1623 msgstr ""
1624 1624
1625 1625 #: rhodecode/events/pullrequest.py:89
1626 1626 msgid "Event triggered after pull request was closed"
1627 1627 msgstr ""
1628 1628
1629 1629 #: rhodecode/events/pullrequest.py:98
1630 1630 msgid "pullrequest commits updated"
1631 1631 msgstr ""
1632 1632
1633 1633 #: rhodecode/events/pullrequest.py:99
1634 1634 msgid "Event triggered after pull requests was updated"
1635 1635 msgstr ""
1636 1636
1637 1637 #: rhodecode/events/pullrequest.py:108
1638 1638 msgid "pullrequest review changed"
1639 1639 msgstr ""
1640 1640
1641 1641 #: rhodecode/events/pullrequest.py:109
1642 1642 msgid "Event triggered after a review status of a pull requests has changed to other."
1643 1643 msgstr ""
1644 1644
1645 1645 #: rhodecode/events/pullrequest.py:123
1646 1646 msgid "pullrequest merged"
1647 1647 msgstr ""
1648 1648
1649 1649 #: rhodecode/events/pullrequest.py:124
1650 1650 msgid "Event triggered after a successful merge operation was executed on a pull request"
1651 1651 msgstr ""
1652 1652
1653 1653 #: rhodecode/events/pullrequest.py:134
1654 1654 msgid "pullrequest commented"
1655 1655 msgstr ""
1656 1656
1657 1657 #: rhodecode/events/pullrequest.py:135
1658 1658 msgid "Event triggered after a comment was made on a code in the pull request"
1659 1659 msgstr ""
1660 1660
1661 1661 #: rhodecode/events/pullrequest.py:173
1662 1662 msgid "pullrequest comment edited"
1663 1663 msgstr ""
1664 1664
1665 1665 #: rhodecode/events/pullrequest.py:174
1666 1666 msgid "Event triggered after a comment was edited on a code in the pull request"
1667 1667 msgstr ""
1668 1668
1669 1669 #: rhodecode/events/repo.py:191
1670 1670 msgid "repository commit comment"
1671 1671 msgstr ""
1672 1672
1673 1673 #: rhodecode/events/repo.py:192
1674 1674 msgid "Event triggered after a comment was made on commit inside a repository"
1675 1675 msgstr ""
1676 1676
1677 1677 #: rhodecode/events/repo.py:226
1678 1678 msgid "repository commit edit comment"
1679 1679 msgstr ""
1680 1680
1681 1681 #: rhodecode/events/repo.py:227
1682 1682 msgid "Event triggered after a comment was edited on commit inside a repository"
1683 1683 msgstr ""
1684 1684
1685 1685 #: rhodecode/events/repo.py:260
1686 1686 msgid "repository pre create"
1687 1687 msgstr ""
1688 1688
1689 1689 #: rhodecode/events/repo.py:261
1690 1690 msgid "Event triggered before repository is created"
1691 1691 msgstr ""
1692 1692
1693 1693 #: rhodecode/events/repo.py:270
1694 1694 msgid "repository created"
1695 1695 msgstr ""
1696 1696
1697 1697 #: rhodecode/events/repo.py:271
1698 1698 msgid "Event triggered after repository was created"
1699 1699 msgstr ""
1700 1700
1701 1701 #: rhodecode/events/repo.py:280
1702 1702 msgid "repository pre delete"
1703 1703 msgstr ""
1704 1704
1705 1705 #: rhodecode/events/repo.py:281
1706 1706 msgid "Event triggered before a repository is deleted"
1707 1707 msgstr ""
1708 1708
1709 1709 #: rhodecode/events/repo.py:290
1710 1710 msgid "repository deleted"
1711 1711 msgstr ""
1712 1712
1713 1713 #: rhodecode/events/repo.py:291
1714 1714 msgid "Event triggered after repository was deleted"
1715 1715 msgstr ""
1716 1716
1717 1717 #: rhodecode/events/repo.py:331
1718 1718 msgid "repository pre pull"
1719 1719 msgstr ""
1720 1720
1721 1721 #: rhodecode/events/repo.py:332
1722 1722 msgid "Event triggered before repository code is pulled"
1723 1723 msgstr ""
1724 1724
1725 1725 #: rhodecode/events/repo.py:341
1726 1726 msgid "repository pull"
1727 1727 msgstr ""
1728 1728
1729 1729 #: rhodecode/events/repo.py:342
1730 1730 msgid "Event triggered after repository code was pulled"
1731 1731 msgstr ""
1732 1732
1733 1733 #: rhodecode/events/repo.py:351
1734 1734 msgid "repository pre push"
1735 1735 msgstr ""
1736 1736
1737 1737 #: rhodecode/events/repo.py:352
1738 1738 msgid "Event triggered before the code is pushed to a repository"
1739 1739 msgstr ""
1740 1740
1741 1741 #: rhodecode/events/repo.py:364
1742 1742 msgid "repository push"
1743 1743 msgstr ""
1744 1744
1745 1745 #: rhodecode/events/repo.py:365
1746 1746 msgid "Event triggered after the code was pushed to a repository"
1747 1747 msgstr ""
1748 1748
1749 1749 #: rhodecode/events/repo_group.py:62
1750 1750 msgid "repository group created"
1751 1751 msgstr ""
1752 1752
1753 1753 #: rhodecode/events/repo_group.py:63
1754 1754 msgid "Event triggered after a repository group was created"
1755 1755 msgstr ""
1756 1756
1757 1757 #: rhodecode/events/repo_group.py:72
1758 1758 msgid "repository group deleted"
1759 1759 msgstr ""
1760 1760
1761 1761 #: rhodecode/events/repo_group.py:73
1762 1762 msgid "Event triggered after a repository group was deleted"
1763 1763 msgstr ""
1764 1764
1765 1765 #: rhodecode/events/repo_group.py:82
1766 1766 msgid "repository group update"
1767 1767 msgstr ""
1768 1768
1769 1769 #: rhodecode/events/repo_group.py:83
1770 1770 msgid "Event triggered after a repository group was updated"
1771 1771 msgstr ""
1772 1772
1773 1773 #: rhodecode/events/user.py:37
1774 1774 msgid "user registered"
1775 1775 msgstr ""
1776 1776
1777 1777 #: rhodecode/events/user.py:52
1778 1778 msgid "user pre create"
1779 1779 msgstr ""
1780 1780
1781 1781 #: rhodecode/events/user.py:66
1782 1782 msgid "user post create"
1783 1783 msgstr ""
1784 1784
1785 1785 #: rhodecode/events/user.py:80
1786 1786 msgid "user pre update"
1787 1787 msgstr ""
1788 1788
1789 1789 #: rhodecode/events/user.py:100
1790 1790 msgid "user permissions change"
1791 1791 msgstr ""
1792 1792
1793 1793 #: rhodecode/forms/__init__.py:35
1794 1794 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:103
1795 1795 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:73
1796 1796 #: rhodecode/templates/admin/permissions/permissions_application.mako:60
1797 1797 #: rhodecode/templates/admin/permissions/permissions_ips.mako:64
1798 1798 #: rhodecode/templates/admin/permissions/permissions_objects.mako:60
1799 1799 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:227
1800 1800 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:78
1801 1801 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:66
1802 1802 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:207
1803 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:251
1803 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:252
1804 1804 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:44
1805 1805 #: rhodecode/templates/admin/settings/settings_global.mako:141
1806 1806 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:16
1807 1807 #: rhodecode/templates/admin/settings/settings_labs.mako:49
1808 1808 #: rhodecode/templates/admin/settings/settings_vcs.mako:14
1809 1809 #: rhodecode/templates/admin/settings/settings_visual.mako:215
1810 1810 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:217
1811 1811 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:107
1812 1812 #: rhodecode/templates/admin/users/user_edit_emails.mako:66
1813 1813 #: rhodecode/templates/admin/users/user_edit_ips.mako:76
1814 1814 #: rhodecode/templates/admin/users/user_edit_profile.mako:155
1815 1815 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:72
1816 1816 #: rhodecode/templates/base/default_perms_box.mako:102
1817 1817 msgid "Reset"
1818 1818 msgstr ""
1819 1819
1820 1820 #: rhodecode/forms/__init__.py:36 rhodecode/public/js/scripts.js:38315
1821 1821 #: rhodecode/public/js/scripts.min.js:1
1822 1822 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:29
1823 1823 #: rhodecode/public/js/src/rhodecode/utils/ajax.js:165
1824 1824 #: rhodecode/templates/admin/gists/gist_show.mako:59
1825 1825 #: rhodecode/templates/admin/integrations/list.mako:179
1826 1826 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:65
1827 1827 #: rhodecode/templates/admin/my_account/my_account_emails.mako:33
1828 1828 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:34
1829 1829 #: rhodecode/templates/admin/notifications/notifications_data.mako:22
1830 1830 #: rhodecode/templates/admin/notifications/notifications_show.mako:37
1831 1831 #: rhodecode/templates/admin/permissions/permissions_ips.mako:29
1832 1832 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:25
1833 1833 #: rhodecode/templates/admin/settings/settings_hooks.mako:46
1834 1834 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:69
1835 1835 #: rhodecode/templates/admin/users/user_edit_emails.mako:34
1836 1836 #: rhodecode/templates/admin/users/user_edit_ips.mako:40
1837 1837 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:35
1838 1838 #: rhodecode/templates/base/issue_tracker_settings.mako:147
1839 1839 #: rhodecode/templates/base/vcs_settings.mako:244
1840 1840 #: rhodecode/templates/base/vcs_settings.mako:269
1841 1841 #: rhodecode/templates/changeset/changeset_file_comment.mako:236
1842 1842 #: rhodecode/templates/changeset/changeset_file_comment.mako:250
1843 1843 #: rhodecode/templates/changeset/changeset_file_comment.mako:259
1844 1844 #: rhodecode/templates/data_table/_dt_elements.mako:439
1845 1845 #: rhodecode/templates/debug_style/buttons.html:132
1846 1846 #: rhodecode/templates/files/files_source.mako:40
1847 1847 #: rhodecode/templates/files/files_source.mako:47
1848 1848 #: rhodecode/templates/files/files_source.mako:53
1849 1849 msgid "Delete"
1850 1850 msgstr ""
1851 1851
1852 1852 #: rhodecode/integrations/schema.py:30
1853 1853 #: rhodecode/model/validation_schema/schemas/integration_schema.py:193
1854 1854 msgid "Enable or disable this integration."
1855 1855 msgstr ""
1856 1856
1857 1857 #: rhodecode/integrations/schema.py:37
1858 1858 #: rhodecode/model/validation_schema/schemas/integration_schema.py:174
1859 1859 msgid "Short name for this integration."
1860 1860 msgstr ""
1861 1861
1862 1862 #: rhodecode/integrations/schema.py:39
1863 1863 #: rhodecode/model/validation_schema/schemas/integration_schema.py:176
1864 1864 msgid "Integration name"
1865 1865 msgstr ""
1866 1866
1867 1867 #: rhodecode/integrations/schema.py:51
1868 1868 msgid "Limit integrations to to work only on the direct children repositories of this repository group (no subgroups)"
1869 1869 msgstr ""
1870 1870
1871 1871 #: rhodecode/integrations/schema.py:55
1872 1872 msgid "Limit to childen repos only"
1873 1873 msgstr ""
1874 1874
1875 1875 #: rhodecode/integrations/schema.py:63
1876 1876 msgid "Limit integrations to to work only on root level repositories"
1877 1877 msgstr ""
1878 1878
1879 1879 #: rhodecode/integrations/schema.py:66
1880 1880 msgid "Root repositories only"
1881 1881 msgstr ""
1882 1882
1883 1883 #: rhodecode/integrations/views.py:147
1884 1884 msgid "{repo_name} repository"
1885 1885 msgstr ""
1886 1886
1887 1887 #: rhodecode/integrations/views.py:150
1888 1888 msgid "{repo_group_name} repo group"
1889 1889 msgstr ""
1890 1890
1891 1891 #: rhodecode/integrations/views.py:153
1892 1892 #: rhodecode/templates/admin/permissions/permissions.mako:36
1893 1893 msgid "Global"
1894 1894 msgstr ""
1895 1895
1896 1896 #: rhodecode/integrations/views.py:157
1897 1897 msgid "{name} integration"
1898 1898 msgstr ""
1899 1899
1900 1900 #: rhodecode/integrations/views.py:172
1901 1901 msgid "Integration {integration_name} deleted successfully."
1902 1902 msgstr ""
1903 1903
1904 1904 #: rhodecode/integrations/views.py:305
1905 1905 msgid "Errors exist when saving integration settings. Please check the form inputs."
1906 1906 msgstr ""
1907 1907
1908 1908 #: rhodecode/integrations/views.py:330
1909 1909 msgid "Integration {integration_name} updated successfully."
1910 1910 msgstr ""
1911 1911
1912 1912 #: rhodecode/integrations/types/email.py:157
1913 1913 msgid "Recipients"
1914 1914 msgstr ""
1915 1915
1916 1916 #: rhodecode/integrations/types/email.py:158
1917 1917 msgid "Email addresses to send push events to"
1918 1918 msgstr ""
1919 1919
1920 1920 #: rhodecode/integrations/types/email.py:163
1921 1921 #: rhodecode/integrations/types/email.py:164
1922 1922 msgid "Email address"
1923 1923 msgstr ""
1924 1924
1925 1925 #: rhodecode/integrations/types/email.py:175
1926 1926 #: rhodecode/templates/register.mako:97
1927 1927 #: rhodecode/templates/admin/my_account/my_account_profile.mako:78
1928 1928 #: rhodecode/templates/admin/users/user_add.mako:86
1929 1929 #: rhodecode/templates/admin/users/user_edit_profile.mako:65
1930 1930 #: rhodecode/templates/admin/users/users.mako:76
1931 1931 #: rhodecode/templates/email_templates/user_registration.mako:51
1932 1932 #: rhodecode/templates/users/user_profile.mako:69
1933 1933 msgid "Email"
1934 1934 msgstr ""
1935 1935
1936 1936 #: rhodecode/integrations/types/email.py:176
1937 1937 msgid "Send repo push summaries to a list of recipients via email"
1938 1938 msgstr ""
1939 1939
1940 1940 #: rhodecode/integrations/types/hipchat.py:63
1941 1941 msgid "Yellow"
1942 1942 msgstr ""
1943 1943
1944 1944 #: rhodecode/integrations/types/hipchat.py:64
1945 1945 msgid "Red"
1946 1946 msgstr ""
1947 1947
1948 1948 #: rhodecode/integrations/types/hipchat.py:65
1949 1949 msgid "Green"
1950 1950 msgstr ""
1951 1951
1952 1952 #: rhodecode/integrations/types/hipchat.py:66
1953 1953 msgid "Purple"
1954 1954 msgstr ""
1955 1955
1956 1956 #: rhodecode/integrations/types/hipchat.py:67
1957 1957 msgid "Gray"
1958 1958 msgstr ""
1959 1959
1960 1960 #: rhodecode/integrations/types/hipchat.py:72
1961 1961 msgid "Hipchat server URL"
1962 1962 msgstr ""
1963 1963
1964 1964 #: rhodecode/integrations/types/hipchat.py:73
1965 1965 msgid "Hipchat integration url."
1966 1966 msgstr ""
1967 1967
1968 1968 #: rhodecode/integrations/types/hipchat.py:83
1969 1969 msgid "Notify"
1970 1970 msgstr ""
1971 1971
1972 1972 #: rhodecode/integrations/types/hipchat.py:84
1973 1973 msgid "Make a notification to the users in room."
1974 1974 msgstr ""
1975 1975
1976 1976 #: rhodecode/integrations/types/hipchat.py:90
1977 1977 msgid "Color"
1978 1978 msgstr ""
1979 1979
1980 1980 #: rhodecode/integrations/types/hipchat.py:91
1981 1981 msgid "Background color of message."
1982 1982 msgstr ""
1983 1983
1984 1984 #: rhodecode/integrations/types/hipchat.py:102
1985 1985 msgid "Hipchat"
1986 1986 msgstr ""
1987 1987
1988 1988 #: rhodecode/integrations/types/hipchat.py:103
1989 1989 msgid "Send events such as repo pushes and pull requests to your hipchat channel."
1990 1990 msgstr ""
1991 1991
1992 1992 #: rhodecode/integrations/types/slack.py:72
1993 1993 msgid "Slack service URL"
1994 1994 msgstr ""
1995 1995
1996 1996 #: rhodecode/integrations/types/slack.py:73
1997 1997 msgid "This can be setup at the <a href=\"https://my.slack.com/services/new/incoming-webhook/\">slack app manager</a>"
1998 1998 msgstr ""
1999 1999
2000 2000 #: rhodecode/integrations/types/slack.py:86
2001 2001 #: rhodecode/integrations/types/webhook.py:81 rhodecode/templates/login.mako:44
2002 2002 #: rhodecode/templates/register.mako:50
2003 2003 #: rhodecode/templates/admin/admin_log_base.mako:7
2004 2004 #: rhodecode/templates/admin/my_account/my_account_profile.mako:38
2005 2005 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:29
2006 2006 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:45
2007 2007 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:74
2008 2008 #: rhodecode/templates/admin/users/user_add.mako:35
2009 2009 #: rhodecode/templates/admin/users/user_edit_profile.mako:39
2010 2010 #: rhodecode/templates/admin/users/users.mako:74
2011 2011 #: rhodecode/templates/debug_style/login.html:36
2012 2012 #: rhodecode/templates/email_templates/user_registration.mako:43
2013 2013 #: rhodecode/templates/user_group/profile.mako:61
2014 2014 #: rhodecode/templates/users/user_profile.mako:29
2015 2015 msgid "Username"
2016 2016 msgstr ""
2017 2017
2018 2018 #: rhodecode/integrations/types/slack.py:87
2019 2019 msgid "Username to show notifications coming from."
2020 2020 msgstr ""
2021 2021
2022 2022 #: rhodecode/integrations/types/slack.py:96
2023 2023 msgid "Channel"
2024 2024 msgstr ""
2025 2025
2026 2026 #: rhodecode/integrations/types/slack.py:97
2027 2027 msgid "Channel to send notifications to."
2028 2028 msgstr ""
2029 2029
2030 2030 #: rhodecode/integrations/types/slack.py:106
2031 2031 msgid "Emoji"
2032 2032 msgstr ""
2033 2033
2034 2034 #: rhodecode/integrations/types/slack.py:107
2035 2035 msgid "Emoji to use eg. :studio_microphone:"
2036 2036 msgstr ""
2037 2037
2038 2038 #: rhodecode/integrations/types/slack.py:118
2039 2039 msgid "Slack"
2040 2040 msgstr ""
2041 2041
2042 2042 #: rhodecode/integrations/types/slack.py:119
2043 2043 msgid "Send events such as repo pushes and pull requests to your slack channel."
2044 2044 msgstr ""
2045 2045
2046 2046 #: rhodecode/integrations/types/webhook.py:49
2047 2047 msgid "Webhook URL"
2048 2048 msgstr ""
2049 2049
2050 2050 #: rhodecode/integrations/types/webhook.py:51
2051 2051 msgid "URL to which Webhook should submit data. If used some of the variables would trigger multiple calls, like ${branch} or ${commit_id}. Webhook will be called as many times as unique objects in data in such cases."
2052 2052 msgstr ""
2053 2053
2054 2054 #: rhodecode/integrations/types/webhook.py:70
2055 2055 msgid "Secret Token"
2056 2056 msgstr ""
2057 2057
2058 2058 #: rhodecode/integrations/types/webhook.py:71
2059 2059 msgid "Optional string used to validate received payloads. It will be sent together with event data in JSON"
2060 2060 msgstr ""
2061 2061
2062 2062 #: rhodecode/integrations/types/webhook.py:82
2063 2063 msgid "Optional username to authenticate the call."
2064 2064 msgstr ""
2065 2065
2066 2066 #: rhodecode/integrations/types/webhook.py:91 rhodecode/templates/login.mako:51
2067 2067 #: rhodecode/templates/register.mako:64
2068 2068 #: rhodecode/templates/admin/my_account/my_account.mako:31
2069 2069 #: rhodecode/templates/admin/users/user_add.mako:44
2070 2070 #: rhodecode/templates/debug_style/login.html:45
2071 2071 msgid "Password"
2072 2072 msgstr ""
2073 2073
2074 2074 #: rhodecode/integrations/types/webhook.py:92
2075 2075 msgid "Optional password to authenticate the call."
2076 2076 msgstr ""
2077 2077
2078 2078 #: rhodecode/integrations/types/webhook.py:102
2079 2079 msgid "Custom Header Key"
2080 2080 msgstr ""
2081 2081
2082 2082 #: rhodecode/integrations/types/webhook.py:103
2083 2083 msgid "Custom Header name to be set when calling endpoint."
2084 2084 msgstr ""
2085 2085
2086 2086 #: rhodecode/integrations/types/webhook.py:112
2087 2087 msgid "Custom Header Value"
2088 2088 msgstr ""
2089 2089
2090 2090 #: rhodecode/integrations/types/webhook.py:113
2091 2091 msgid "Custom Header value to be set when calling endpoint."
2092 2092 msgstr ""
2093 2093
2094 2094 #: rhodecode/integrations/types/webhook.py:122
2095 2095 msgid "Call Method"
2096 2096 msgstr ""
2097 2097
2098 2098 #: rhodecode/integrations/types/webhook.py:123
2099 2099 msgid "Select a HTTP method to use when calling the Webhook."
2100 2100 msgstr ""
2101 2101
2102 2102 #: rhodecode/integrations/types/webhook.py:135
2103 2103 msgid "Webhook"
2104 2104 msgstr ""
2105 2105
2106 2106 #: rhodecode/integrations/types/webhook.py:136
2107 2107 msgid "send JSON data to a url endpoint"
2108 2108 msgstr ""
2109 2109
2110 2110 #: rhodecode/lib/action_parser.py:93
2111 2111 msgid "[deleted] repository"
2112 2112 msgstr ""
2113 2113
2114 2114 #: rhodecode/lib/action_parser.py:96 rhodecode/lib/action_parser.py:114
2115 2115 msgid "[created] repository"
2116 2116 msgstr ""
2117 2117
2118 2118 #: rhodecode/lib/action_parser.py:99
2119 2119 msgid "[created] repository as fork"
2120 2120 msgstr ""
2121 2121
2122 2122 #: rhodecode/lib/action_parser.py:102 rhodecode/lib/action_parser.py:117
2123 2123 msgid "[forked] repository"
2124 2124 msgstr ""
2125 2125
2126 2126 #: rhodecode/lib/action_parser.py:105 rhodecode/lib/action_parser.py:120
2127 2127 msgid "[updated] repository"
2128 2128 msgstr ""
2129 2129
2130 2130 #: rhodecode/lib/action_parser.py:108
2131 2131 msgid "[downloaded] archive from repository"
2132 2132 msgstr ""
2133 2133
2134 2134 #: rhodecode/lib/action_parser.py:111
2135 2135 msgid "[delete] repository"
2136 2136 msgstr ""
2137 2137
2138 2138 #: rhodecode/lib/action_parser.py:123
2139 2139 msgid "[created] user"
2140 2140 msgstr ""
2141 2141
2142 2142 #: rhodecode/lib/action_parser.py:126
2143 2143 msgid "[updated] user"
2144 2144 msgstr ""
2145 2145
2146 2146 #: rhodecode/lib/action_parser.py:129
2147 2147 msgid "[created] user group"
2148 2148 msgstr ""
2149 2149
2150 2150 #: rhodecode/lib/action_parser.py:132
2151 2151 msgid "[updated] user group"
2152 2152 msgstr ""
2153 2153
2154 2154 #: rhodecode/lib/action_parser.py:135
2155 2155 msgid "[commented] on commit in repository"
2156 2156 msgstr ""
2157 2157
2158 2158 #: rhodecode/lib/action_parser.py:138
2159 2159 msgid "[commented] on pull request for"
2160 2160 msgstr ""
2161 2161
2162 2162 #: rhodecode/lib/action_parser.py:141
2163 2163 msgid "[closed] pull request for"
2164 2164 msgstr ""
2165 2165
2166 2166 #: rhodecode/lib/action_parser.py:144
2167 2167 msgid "[merged] pull request for"
2168 2168 msgstr ""
2169 2169
2170 2170 #: rhodecode/lib/action_parser.py:147
2171 2171 msgid "[pushed] into"
2172 2172 msgstr ""
2173 2173
2174 2174 #: rhodecode/lib/action_parser.py:150
2175 2175 msgid "[committed via RhodeCode] into repository"
2176 2176 msgstr ""
2177 2177
2178 2178 #: rhodecode/lib/action_parser.py:153
2179 2179 msgid "[pulled from remote] into repository"
2180 2180 msgstr ""
2181 2181
2182 2182 #: rhodecode/lib/action_parser.py:156
2183 2183 msgid "[pulled] from"
2184 2184 msgstr ""
2185 2185
2186 2186 #: rhodecode/lib/action_parser.py:159
2187 2187 msgid "[started following] repository"
2188 2188 msgstr ""
2189 2189
2190 2190 #: rhodecode/lib/action_parser.py:162
2191 2191 msgid "[stopped following] repository"
2192 2192 msgstr ""
2193 2193
2194 2194 #: rhodecode/lib/action_parser.py:172
2195 2195 #, python-format
2196 2196 msgid "fork name %s"
2197 2197 msgstr ""
2198 2198
2199 2199 #: rhodecode/lib/action_parser.py:191
2200 2200 #, python-format
2201 2201 msgid "Pull request #%s"
2202 2202 msgstr ""
2203 2203
2204 2204 #: rhodecode/lib/action_parser.py:226
2205 2205 #, python-format
2206 2206 msgid "Show all combined commits %s->%s"
2207 2207 msgstr ""
2208 2208
2209 2209 #: rhodecode/lib/action_parser.py:231
2210 2210 msgid "compare view"
2211 2211 msgstr ""
2212 2212
2213 2213 #: rhodecode/lib/action_parser.py:238
2214 2214 #, python-format
2215 2215 msgid " and %(num)s more commits"
2216 2216 msgstr ""
2217 2217
2218 2218 #: rhodecode/lib/action_parser.py:293
2219 2219 #, python-format
2220 2220 msgid "Deleted branch: %s"
2221 2221 msgstr ""
2222 2222
2223 2223 #: rhodecode/lib/action_parser.py:296
2224 2224 #, python-format
2225 2225 msgid "Created tag: %s"
2226 2226 msgstr ""
2227 2227
2228 2228 #: rhodecode/lib/action_parser.py:309
2229 2229 msgid "Commit not found"
2230 2230 msgstr ""
2231 2231
2232 2232 #: rhodecode/lib/auth.py:1769
2233 2233 msgid "IP {} not allowed"
2234 2234 msgstr ""
2235 2235
2236 2236 #: rhodecode/lib/auth.py:1861
2237 2237 msgid "You need to be a registered user to perform this action"
2238 2238 msgstr ""
2239 2239
2240 2240 #: rhodecode/lib/auth.py:1905
2241 2241 msgid "You need to be signed in to view this page"
2242 2242 msgstr ""
2243 2243
2244 2244 #: rhodecode/lib/channelstream.py:318
2245 2245 msgid " Reload page to load changes"
2246 2246 msgstr ""
2247 2247
2248 2248 #: rhodecode/lib/diffs.py:903
2249 2249 msgid "Click to select line"
2250 2250 msgstr ""
2251 2251
2252 2252 #: rhodecode/lib/helpers.py:1878
2253 2253 msgid ""
2254 2254 "Example filter terms:\n"
2255 2255 " repository:vcs\n"
2256 2256 " username:marcin\n"
2257 2257 " username:(NOT marcin)\n"
2258 2258 " action:*push*\n"
2259 2259 " ip:127.0.0.1\n"
2260 2260 " date:20120101\n"
2261 2261 " date:[20120101100000 TO 20120102]\n"
2262 2262 "\n"
2263 2263 "Actions: {actions}\n"
2264 2264 "\n"
2265 2265 "Generate wildcards using '*' character:\n"
2266 2266 " \"repository:vcs*\" - search everything starting with 'vcs'\n"
2267 2267 " \"repository:*vcs*\" - search for repository containing 'vcs'\n"
2268 2268 "\n"
2269 2269 "Optional AND / OR operators in queries\n"
2270 2270 " \"repository:vcs OR repository:test\"\n"
2271 2271 " \"username:test AND repository:test*\"\n"
2272 2272 msgstr ""
2273 2273
2274 2274 #: rhodecode/lib/helpers.py:1902
2275 2275 #, python-format
2276 2276 msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
2277 2277 msgstr ""
2278 2278
2279 2279 #: rhodecode/lib/utils2.py:542
2280 2280 msgid "in ${ago}"
2281 2281 msgstr ""
2282 2282
2283 2283 #: rhodecode/lib/utils2.py:548
2284 2284 msgid "${ago} ago"
2285 2285 msgstr ""
2286 2286
2287 2287 #: rhodecode/lib/utils2.py:557
2288 2288 msgid "${val}, ${detail}"
2289 2289 msgstr ""
2290 2290
2291 2291 #: rhodecode/lib/utils2.py:559
2292 2292 msgid "${val}, ${detail} ago"
2293 2293 msgstr ""
2294 2294
2295 2295 #: rhodecode/lib/utils2.py:561
2296 2296 msgid "in ${val}, ${detail}"
2297 2297 msgstr ""
2298 2298
2299 2299 #: rhodecode/lib/utils2.py:563
2300 2300 msgid "${val} and ${detail}"
2301 2301 msgstr ""
2302 2302
2303 2303 #: rhodecode/lib/utils2.py:565
2304 2304 msgid "${val} and ${detail} ago"
2305 2305 msgstr ""
2306 2306
2307 2307 #: rhodecode/lib/utils2.py:567
2308 2308 msgid "in ${val} and ${detail}"
2309 2309 msgstr ""
2310 2310
2311 2311 #: rhodecode/lib/utils2.py:571 rhodecode/public/js/scripts.js:22612
2312 2312 #: rhodecode/public/js/scripts.min.js:1
2313 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:136
2313 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:145
2314 2314 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:174
2315 2315 msgid "just now"
2316 2316 msgstr ""
2317 2317
2318 2318 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:568
2319 2319 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:581
2320 2320 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:598
2321 2321 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:681
2322 2322 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:656
2323 2323 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:658
2324 2324 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:681
2325 2325 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:682
2326 2326 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:699
2327 2327 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:716
2328 2328 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:732
2329 2329 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:735
2330 2330 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:737
2331 2331 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:737
2332 2332 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:764
2333 2333 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:774
2334 2334 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:815
2335 2335 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:815
2336 2336 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:816
2337 2337 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:816
2338 2338 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:822
2339 2339 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:944
2340 2340 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:969
2341 2341 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2587
2342 2342 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2653
2343 2343 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2739
2344 2344 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2772
2345 2345 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2772
2346 2346 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2987
2347 2347 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3073
2348 2348 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3079
2349 2349 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3110
2350 2350 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2275
2351 2351 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2267
2352 2352 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2266
2353 2353 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2270
2354 2354 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2270
2355 2355 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2321
2356 2356 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2322
2357 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2522 rhodecode/model/db.py:3111
2357 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2522 rhodecode/model/db.py:3132
2358 2358 msgid "Repository no access"
2359 2359 msgstr ""
2360 2360
2361 2361 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:569
2362 2362 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:582
2363 2363 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:599
2364 2364 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:682
2365 2365 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:657
2366 2366 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:659
2367 2367 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:682
2368 2368 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:683
2369 2369 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:700
2370 2370 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:717
2371 2371 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:733
2372 2372 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:736
2373 2373 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:738
2374 2374 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:738
2375 2375 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:765
2376 2376 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:775
2377 2377 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:816
2378 2378 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:816
2379 2379 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:817
2380 2380 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:817
2381 2381 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:823
2382 2382 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:945
2383 2383 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:970
2384 2384 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2588
2385 2385 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2654
2386 2386 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2740
2387 2387 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2773
2388 2388 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2773
2389 2389 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2988
2390 2390 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3074
2391 2391 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3080
2392 2392 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3111
2393 2393 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2276
2394 2394 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2268
2395 2395 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2267
2396 2396 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2271
2397 2397 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2271
2398 2398 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2322
2399 2399 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2323
2400 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2523 rhodecode/model/db.py:3112
2400 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2523 rhodecode/model/db.py:3133
2401 2401 msgid "Repository read access"
2402 2402 msgstr ""
2403 2403
2404 2404 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:570
2405 2405 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:583
2406 2406 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:600
2407 2407 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:683
2408 2408 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:658
2409 2409 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:660
2410 2410 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:683
2411 2411 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:684
2412 2412 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:701
2413 2413 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:718
2414 2414 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:734
2415 2415 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:737
2416 2416 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:739
2417 2417 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:739
2418 2418 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:766
2419 2419 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:776
2420 2420 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:817
2421 2421 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:817
2422 2422 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:818
2423 2423 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:818
2424 2424 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:824
2425 2425 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:946
2426 2426 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:971
2427 2427 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2589
2428 2428 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2655
2429 2429 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2741
2430 2430 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2774
2431 2431 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2774
2432 2432 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2989
2433 2433 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3075
2434 2434 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3081
2435 2435 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3112
2436 2436 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2277
2437 2437 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2269
2438 2438 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2268
2439 2439 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2272
2440 2440 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2272
2441 2441 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2323
2442 2442 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2324
2443 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2524 rhodecode/model/db.py:3113
2443 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2524 rhodecode/model/db.py:3134
2444 2444 msgid "Repository write access"
2445 2445 msgstr ""
2446 2446
2447 2447 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:571
2448 2448 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:584
2449 2449 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:601
2450 2450 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:684
2451 2451 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:659
2452 2452 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:661
2453 2453 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:684
2454 2454 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:685
2455 2455 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:702
2456 2456 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:719
2457 2457 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:735
2458 2458 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:738
2459 2459 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:740
2460 2460 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:740
2461 2461 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:767
2462 2462 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:777
2463 2463 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:818
2464 2464 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:818
2465 2465 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:819
2466 2466 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:819
2467 2467 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:825
2468 2468 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:947
2469 2469 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:972
2470 2470 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2590
2471 2471 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2656
2472 2472 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2742
2473 2473 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2775
2474 2474 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2775
2475 2475 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2990
2476 2476 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3076
2477 2477 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3082
2478 2478 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3113
2479 2479 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2278
2480 2480 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2270
2481 2481 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2269
2482 2482 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2273
2483 2483 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2273
2484 2484 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2324
2485 2485 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2325
2486 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2525 rhodecode/model/db.py:3114
2486 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2525 rhodecode/model/db.py:3135
2487 2487 msgid "Repository admin access"
2488 2488 msgstr ""
2489 2489
2490 2490 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:573
2491 2491 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:586
2492 2492 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:603
2493 2493 msgid "Repositories Group no access"
2494 2494 msgstr ""
2495 2495
2496 2496 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:574
2497 2497 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:587
2498 2498 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:604
2499 2499 msgid "Repositories Group read access"
2500 2500 msgstr ""
2501 2501
2502 2502 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:575
2503 2503 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:588
2504 2504 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:605
2505 2505 msgid "Repositories Group write access"
2506 2506 msgstr ""
2507 2507
2508 2508 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:576
2509 2509 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:589
2510 2510 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:606
2511 2511 msgid "Repositories Group admin access"
2512 2512 msgstr ""
2513 2513
2514 2514 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:578
2515 2515 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:591
2516 2516 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:608
2517 2517 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:691
2518 2518 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:654
2519 2519 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:656
2520 2520 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:679
2521 2521 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:680
2522 2522 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:697
2523 2523 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:714
2524 2524 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:730
2525 2525 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:733
2526 2526 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:735
2527 2527 msgid "RhodeCode Administrator"
2528 2528 msgstr ""
2529 2529
2530 2530 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:579
2531 2531 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:592
2532 2532 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:609
2533 2533 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:692
2534 2534 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:677
2535 2535 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:679
2536 2536 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:702
2537 2537 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:703
2538 2538 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:720
2539 2539 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:737
2540 2540 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:753
2541 2541 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:756
2542 2542 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:758
2543 2543 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:758
2544 2544 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:785
2545 2545 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:795
2546 2546 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:836
2547 2547 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:836
2548 2548 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:837
2549 2549 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:837
2550 2550 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:843
2551 2551 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:965
2552 2552 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:990
2553 2553 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2608
2554 2554 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2679
2555 2555 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2765
2556 2556 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2798
2557 2557 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2798
2558 2558 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3013
2559 2559 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3099
2560 2560 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3105
2561 2561 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3136
2562 2562 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2296
2563 2563 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2288
2564 2564 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2287
2565 2565 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2291
2566 2566 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2291
2567 2567 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2342
2568 2568 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2343
2569 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2543 rhodecode/model/db.py:3137
2569 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2543 rhodecode/model/db.py:3158
2570 2570 msgid "Repository creation disabled"
2571 2571 msgstr ""
2572 2572
2573 2573 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:580
2574 2574 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:593
2575 2575 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:610
2576 2576 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:693
2577 2577 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:678
2578 2578 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:680
2579 2579 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:703
2580 2580 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:704
2581 2581 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:721
2582 2582 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:738
2583 2583 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:754
2584 2584 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:757
2585 2585 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:759
2586 2586 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:759
2587 2587 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:786
2588 2588 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:796
2589 2589 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:837
2590 2590 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:837
2591 2591 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:838
2592 2592 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:838
2593 2593 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:844
2594 2594 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:966
2595 2595 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:991
2596 2596 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2609
2597 2597 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2680
2598 2598 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2766
2599 2599 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2799
2600 2600 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2799
2601 2601 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3014
2602 2602 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3100
2603 2603 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3106
2604 2604 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3137
2605 2605 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2297
2606 2606 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2289
2607 2607 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2288
2608 2608 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2292
2609 2609 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2292
2610 2610 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2343
2611 2611 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2344
2612 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2544 rhodecode/model/db.py:3138
2612 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2544 rhodecode/model/db.py:3159
2613 2613 msgid "Repository creation enabled"
2614 2614 msgstr ""
2615 2615
2616 2616 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:581
2617 2617 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:594
2618 2618 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:611
2619 2619 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:694
2620 2620 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:680
2621 2621 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:682
2622 2622 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:705
2623 2623 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:706
2624 2624 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:723
2625 2625 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:740
2626 2626 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:758
2627 2627 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:761
2628 2628 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:763
2629 2629 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:763
2630 2630 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:790
2631 2631 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:800
2632 2632 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:841
2633 2633 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:841
2634 2634 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:842
2635 2635 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:842
2636 2636 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:848
2637 2637 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:970
2638 2638 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:995
2639 2639 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2613
2640 2640 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2684
2641 2641 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2770
2642 2642 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2803
2643 2643 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2803
2644 2644 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3018
2645 2645 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3104
2646 2646 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3110
2647 2647 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3141
2648 2648 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2301
2649 2649 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2293
2650 2650 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2292
2651 2651 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2296
2652 2652 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2296
2653 2653 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2347
2654 2654 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2348
2655 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2548 rhodecode/model/db.py:3142
2655 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2548 rhodecode/model/db.py:3163
2656 2656 msgid "Repository forking disabled"
2657 2657 msgstr ""
2658 2658
2659 2659 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:582
2660 2660 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:595
2661 2661 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:612
2662 2662 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:695
2663 2663 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:681
2664 2664 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:683
2665 2665 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:706
2666 2666 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:707
2667 2667 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:724
2668 2668 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:741
2669 2669 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:759
2670 2670 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:762
2671 2671 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:764
2672 2672 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:764
2673 2673 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:791
2674 2674 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:801
2675 2675 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:842
2676 2676 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:842
2677 2677 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:843
2678 2678 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:843
2679 2679 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:849
2680 2680 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:971
2681 2681 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:996
2682 2682 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2614
2683 2683 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2685
2684 2684 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2771
2685 2685 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2804
2686 2686 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2804
2687 2687 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3019
2688 2688 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3105
2689 2689 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3111
2690 2690 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3142
2691 2691 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2302
2692 2692 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2294
2693 2693 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2293
2694 2694 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2297
2695 2695 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2297
2696 2696 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2348
2697 2697 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2349
2698 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2549 rhodecode/model/db.py:3143
2698 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2549 rhodecode/model/db.py:3164
2699 2699 msgid "Repository forking enabled"
2700 2700 msgstr ""
2701 2701
2702 2702 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:583
2703 2703 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:596
2704 2704 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:613
2705 2705 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:696
2706 2706 msgid "Register disabled"
2707 2707 msgstr ""
2708 2708
2709 2709 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:584
2710 2710 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:597
2711 2711 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:614
2712 2712 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:697
2713 2713 msgid "Register new user with RhodeCode with manual activation"
2714 2714 msgstr ""
2715 2715
2716 2716 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:587
2717 2717 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:600
2718 2718 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:617
2719 2719 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:700
2720 2720 msgid "Register new user with RhodeCode with auto activation"
2721 2721 msgstr ""
2722 2722
2723 2723 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:843
2724 2724 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:864
2725 2725 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:873
2726 2726 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:956
2727 2727 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:994
2728 2728 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:996
2729 2729 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1019
2730 2730 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1020
2731 2731 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1037
2732 2732 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1054
2733 2733 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1073
2734 2734 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1076
2735 2735 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1087
2736 2736 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1090
2737 2737 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1120
2738 2738 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1134
2739 2739 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1175
2740 2740 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1175
2741 2741 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1184
2742 2742 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1184
2743 2743 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1194
2744 2744 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1316
2745 2745 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1341
2746 2746 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:3295
2747 2747 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:3378
2748 2748 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:3491
2749 2749 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:3524
2750 2750 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:3525
2751 2751 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3757
2752 2752 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3843
2753 2753 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3890
2754 2754 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3935
2755 2755 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2915
2756 2756 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2907
2757 2757 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2907
2758 2758 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2910
2759 2759 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2910
2760 2760 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3011
2761 2761 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3012
2762 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3230 rhodecode/model/db.py:3972
2763 #: rhodecode/public/js/scripts.js:42424 rhodecode/public/js/scripts.min.js:1
2764 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:70
2762 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3230 rhodecode/model/db.py:3997
2763 #: rhodecode/public/js/scripts.js:42595 rhodecode/public/js/scripts.min.js:1
2764 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:72
2765 2765 #: rhodecode/public/js/src/rhodecode/pullrequests.js:396
2766 2766 msgid "Not Reviewed"
2767 2767 msgstr ""
2768 2768
2769 2769 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:844
2770 2770 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:865
2771 2771 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:874
2772 2772 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:957
2773 2773 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:995
2774 2774 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:997
2775 2775 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1020
2776 2776 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1021
2777 2777 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1038
2778 2778 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1055
2779 2779 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1074
2780 2780 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1077
2781 2781 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1088
2782 2782 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1091
2783 2783 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1121
2784 2784 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1135
2785 2785 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1176
2786 2786 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1176
2787 2787 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1185
2788 2788 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1185
2789 2789 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1195
2790 2790 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1317
2791 2791 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1342
2792 2792 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:3296
2793 2793 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:3379
2794 2794 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:3492
2795 2795 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:3525
2796 2796 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:3526
2797 2797 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3758
2798 2798 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3844
2799 2799 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3891
2800 2800 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3936
2801 2801 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2916
2802 2802 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2908
2803 2803 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2908
2804 2804 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2911
2805 2805 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2911
2806 2806 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3012
2807 2807 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3013
2808 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3231 rhodecode/model/db.py:3973
2808 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3231 rhodecode/model/db.py:3998
2809 2809 msgid "Approved"
2810 2810 msgstr ""
2811 2811
2812 2812 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:845
2813 2813 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:866
2814 2814 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:875
2815 2815 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:958
2816 2816 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:996
2817 2817 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:998
2818 2818 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1021
2819 2819 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1022
2820 2820 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1039
2821 2821 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1056
2822 2822 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1075
2823 2823 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1078
2824 2824 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1089
2825 2825 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1092
2826 2826 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1122
2827 2827 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1136
2828 2828 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1177
2829 2829 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1177
2830 2830 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1186
2831 2831 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1186
2832 2832 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1196
2833 2833 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1318
2834 2834 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1343
2835 2835 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:3297
2836 2836 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:3380
2837 2837 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:3493
2838 2838 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:3526
2839 2839 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:3527
2840 2840 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3759
2841 2841 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3845
2842 2842 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3892
2843 2843 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3937
2844 2844 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2917
2845 2845 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2909
2846 2846 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2909
2847 2847 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2912
2848 2848 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2912
2849 2849 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3013
2850 2850 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3014
2851 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3232 rhodecode/model/db.py:3974
2851 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3232 rhodecode/model/db.py:3999
2852 2852 msgid "Rejected"
2853 2853 msgstr ""
2854 2854
2855 2855 #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:846
2856 2856 #: rhodecode/lib/dbmigrate/schema/db_1_5_0.py:867
2857 2857 #: rhodecode/lib/dbmigrate/schema/db_1_5_2.py:876
2858 2858 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:959
2859 2859 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:997
2860 2860 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:999
2861 2861 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:1022
2862 2862 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:1023
2863 2863 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:1040
2864 2864 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:1057
2865 2865 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:1076
2866 2866 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:1079
2867 2867 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:1090
2868 2868 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:1093
2869 2869 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:1123
2870 2870 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:1137
2871 2871 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:1178
2872 2872 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:1178
2873 2873 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:1187
2874 2874 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1187
2875 2875 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1197
2876 2876 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1319
2877 2877 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1344
2878 2878 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:3298
2879 2879 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:3381
2880 2880 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:3494
2881 2881 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:3527
2882 2882 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:3528
2883 2883 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3760
2884 2884 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3846
2885 2885 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3893
2886 2886 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3938
2887 2887 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2918
2888 2888 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2910
2889 2889 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2910
2890 2890 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2913
2891 2891 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2913
2892 2892 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:3014
2893 2893 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:3015
2894 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3233 rhodecode/model/db.py:3975
2894 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:3233 rhodecode/model/db.py:4000
2895 2895 msgid "Under Review"
2896 2896 msgstr ""
2897 2897
2898 2898 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:686
2899 2899 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:661
2900 2900 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:663
2901 2901 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:686
2902 2902 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:687
2903 2903 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:704
2904 2904 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:721
2905 2905 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:737
2906 2906 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:740
2907 2907 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:742
2908 2908 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:742
2909 2909 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:769
2910 2910 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:779
2911 2911 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:820
2912 2912 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:820
2913 2913 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:821
2914 2914 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:821
2915 2915 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:827
2916 2916 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:949
2917 2917 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:974
2918 2918 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2592
2919 2919 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2658
2920 2920 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2744
2921 2921 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2777
2922 2922 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2777
2923 2923 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2992
2924 2924 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3078
2925 2925 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3084
2926 2926 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3115
2927 2927 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2280
2928 2928 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2272
2929 2929 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2271
2930 2930 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2275
2931 2931 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2275
2932 2932 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2326
2933 2933 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2327
2934 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2527 rhodecode/model/db.py:3116
2934 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2527 rhodecode/model/db.py:3137
2935 2935 msgid "Repository group no access"
2936 2936 msgstr ""
2937 2937
2938 2938 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:687
2939 2939 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:662
2940 2940 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:664
2941 2941 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:687
2942 2942 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:688
2943 2943 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:705
2944 2944 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:722
2945 2945 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:738
2946 2946 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:741
2947 2947 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:743
2948 2948 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:743
2949 2949 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:770
2950 2950 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:780
2951 2951 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:821
2952 2952 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:821
2953 2953 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:822
2954 2954 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:822
2955 2955 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:828
2956 2956 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:950
2957 2957 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:975
2958 2958 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2593
2959 2959 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2659
2960 2960 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2745
2961 2961 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2778
2962 2962 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2778
2963 2963 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2993
2964 2964 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3079
2965 2965 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3085
2966 2966 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3116
2967 2967 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2281
2968 2968 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2273
2969 2969 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2272
2970 2970 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2276
2971 2971 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2276
2972 2972 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2327
2973 2973 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2328
2974 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2528 rhodecode/model/db.py:3117
2974 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2528 rhodecode/model/db.py:3138
2975 2975 msgid "Repository group read access"
2976 2976 msgstr ""
2977 2977
2978 2978 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:688
2979 2979 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:663
2980 2980 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:665
2981 2981 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:688
2982 2982 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:689
2983 2983 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:706
2984 2984 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:723
2985 2985 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:739
2986 2986 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:742
2987 2987 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:744
2988 2988 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:744
2989 2989 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:771
2990 2990 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:781
2991 2991 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:822
2992 2992 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:822
2993 2993 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:823
2994 2994 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:823
2995 2995 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:829
2996 2996 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:951
2997 2997 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:976
2998 2998 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2594
2999 2999 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2660
3000 3000 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2746
3001 3001 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2779
3002 3002 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2779
3003 3003 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2994
3004 3004 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3080
3005 3005 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3086
3006 3006 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3117
3007 3007 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2282
3008 3008 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2274
3009 3009 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2273
3010 3010 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2277
3011 3011 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2277
3012 3012 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2328
3013 3013 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2329
3014 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2529 rhodecode/model/db.py:3118
3014 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2529 rhodecode/model/db.py:3139
3015 3015 msgid "Repository group write access"
3016 3016 msgstr ""
3017 3017
3018 3018 #: rhodecode/lib/dbmigrate/schema/db_1_6_0.py:689
3019 3019 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:664
3020 3020 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:666
3021 3021 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:689
3022 3022 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:690
3023 3023 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:707
3024 3024 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:724
3025 3025 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:740
3026 3026 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:743
3027 3027 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:745
3028 3028 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:745
3029 3029 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:772
3030 3030 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:782
3031 3031 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:823
3032 3032 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:823
3033 3033 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:824
3034 3034 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:824
3035 3035 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:830
3036 3036 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:952
3037 3037 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:977
3038 3038 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2595
3039 3039 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2661
3040 3040 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2747
3041 3041 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2780
3042 3042 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2780
3043 3043 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2995
3044 3044 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3081
3045 3045 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3087
3046 3046 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3118
3047 3047 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2283
3048 3048 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2275
3049 3049 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2274
3050 3050 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2278
3051 3051 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2278
3052 3052 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2329
3053 3053 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2330
3054 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2530 rhodecode/model/db.py:3119
3054 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2530 rhodecode/model/db.py:3140
3055 3055 msgid "Repository group admin access"
3056 3056 msgstr ""
3057 3057
3058 3058 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:666
3059 3059 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:668
3060 3060 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:691
3061 3061 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:692
3062 3062 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:709
3063 3063 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:726
3064 3064 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:742
3065 3065 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:745
3066 3066 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:747
3067 3067 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:747
3068 3068 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:774
3069 3069 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:784
3070 3070 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:825
3071 3071 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:825
3072 3072 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:826
3073 3073 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:826
3074 3074 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:832
3075 3075 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:954
3076 3076 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:979
3077 3077 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2597
3078 3078 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2663
3079 3079 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2749
3080 3080 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2782
3081 3081 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2782
3082 3082 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2997
3083 3083 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3083
3084 3084 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3089
3085 3085 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3120
3086 3086 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2285
3087 3087 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2277
3088 3088 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2276
3089 3089 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2280
3090 3090 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2280
3091 3091 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2331
3092 3092 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2332
3093 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2532 rhodecode/model/db.py:3121
3093 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2532 rhodecode/model/db.py:3142
3094 3094 msgid "User group no access"
3095 3095 msgstr ""
3096 3096
3097 3097 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:667
3098 3098 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:669
3099 3099 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:692
3100 3100 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:693
3101 3101 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:710
3102 3102 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:727
3103 3103 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:743
3104 3104 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:746
3105 3105 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:748
3106 3106 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:748
3107 3107 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:775
3108 3108 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:785
3109 3109 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:826
3110 3110 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:826
3111 3111 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:827
3112 3112 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:827
3113 3113 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:833
3114 3114 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:955
3115 3115 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:980
3116 3116 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2598
3117 3117 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2664
3118 3118 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2750
3119 3119 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2783
3120 3120 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2783
3121 3121 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2998
3122 3122 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3084
3123 3123 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3090
3124 3124 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3121
3125 3125 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2286
3126 3126 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2278
3127 3127 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2277
3128 3128 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2281
3129 3129 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2281
3130 3130 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2332
3131 3131 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2333
3132 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2533 rhodecode/model/db.py:3122
3132 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2533 rhodecode/model/db.py:3143
3133 3133 msgid "User group read access"
3134 3134 msgstr ""
3135 3135
3136 3136 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:668
3137 3137 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:670
3138 3138 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:693
3139 3139 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:694
3140 3140 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:711
3141 3141 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:728
3142 3142 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:744
3143 3143 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:747
3144 3144 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:749
3145 3145 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:749
3146 3146 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:776
3147 3147 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:786
3148 3148 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:827
3149 3149 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:827
3150 3150 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:828
3151 3151 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:828
3152 3152 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:834
3153 3153 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:956
3154 3154 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:981
3155 3155 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2599
3156 3156 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2665
3157 3157 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2751
3158 3158 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2784
3159 3159 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2784
3160 3160 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2999
3161 3161 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3085
3162 3162 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3091
3163 3163 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3122
3164 3164 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2287
3165 3165 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2279
3166 3166 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2278
3167 3167 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2282
3168 3168 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2282
3169 3169 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2333
3170 3170 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2334
3171 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2534 rhodecode/model/db.py:3123
3171 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2534 rhodecode/model/db.py:3144
3172 3172 msgid "User group write access"
3173 3173 msgstr ""
3174 3174
3175 3175 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:669
3176 3176 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:671
3177 3177 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:694
3178 3178 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:695
3179 3179 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:712
3180 3180 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:729
3181 3181 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:745
3182 3182 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:748
3183 3183 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:750
3184 3184 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:750
3185 3185 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:777
3186 3186 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:787
3187 3187 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:828
3188 3188 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:828
3189 3189 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:829
3190 3190 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:829
3191 3191 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:835
3192 3192 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:957
3193 3193 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:982
3194 3194 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2600
3195 3195 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2666
3196 3196 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2752
3197 3197 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2785
3198 3198 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2785
3199 3199 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3000
3200 3200 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3086
3201 3201 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3092
3202 3202 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3123
3203 3203 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2288
3204 3204 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2280
3205 3205 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2279
3206 3206 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2283
3207 3207 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2283
3208 3208 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2334
3209 3209 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2335
3210 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2535 rhodecode/model/db.py:3124
3210 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2535 rhodecode/model/db.py:3145
3211 3211 msgid "User group admin access"
3212 3212 msgstr ""
3213 3213
3214 3214 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:671
3215 3215 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:673
3216 3216 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:696
3217 3217 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:697
3218 3218 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:714
3219 3219 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:731
3220 3220 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:747
3221 3221 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:750
3222 3222 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:752
3223 3223 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:752
3224 3224 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:779
3225 3225 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:789
3226 3226 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:830
3227 3227 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:830
3228 3228 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:831
3229 3229 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:831
3230 3230 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:837
3231 3231 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:959
3232 3232 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:984
3233 3233 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2602
3234 3234 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2673
3235 3235 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2759
3236 3236 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2792
3237 3237 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2792
3238 3238 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3007
3239 3239 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3093
3240 3240 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3099
3241 3241 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3130
3242 3242 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2290
3243 3243 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2282
3244 3244 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2281
3245 3245 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2285
3246 3246 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2285
3247 3247 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2336
3248 3248 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2337
3249 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2537 rhodecode/model/db.py:3131
3249 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2537 rhodecode/model/db.py:3152
3250 3250 msgid "Repository Group creation disabled"
3251 3251 msgstr ""
3252 3252
3253 3253 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:672
3254 3254 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:674
3255 3255 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:697
3256 3256 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:698
3257 3257 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:715
3258 3258 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:732
3259 3259 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:748
3260 3260 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:751
3261 3261 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:753
3262 3262 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:753
3263 3263 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:780
3264 3264 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:790
3265 3265 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:831
3266 3266 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:831
3267 3267 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:832
3268 3268 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:832
3269 3269 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:838
3270 3270 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:960
3271 3271 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:985
3272 3272 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2603
3273 3273 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2674
3274 3274 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2760
3275 3275 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2793
3276 3276 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2793
3277 3277 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3008
3278 3278 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3094
3279 3279 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3100
3280 3280 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3131
3281 3281 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2291
3282 3282 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2283
3283 3283 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2282
3284 3284 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2286
3285 3285 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2286
3286 3286 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2337
3287 3287 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2338
3288 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2538 rhodecode/model/db.py:3132
3288 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2538 rhodecode/model/db.py:3153
3289 3289 msgid "Repository Group creation enabled"
3290 3290 msgstr ""
3291 3291
3292 3292 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:674
3293 3293 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:676
3294 3294 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:699
3295 3295 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:700
3296 3296 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:717
3297 3297 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:734
3298 3298 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:750
3299 3299 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:753
3300 3300 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:755
3301 3301 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:755
3302 3302 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:782
3303 3303 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:792
3304 3304 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:833
3305 3305 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:833
3306 3306 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:834
3307 3307 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:834
3308 3308 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:840
3309 3309 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:962
3310 3310 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:987
3311 3311 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2605
3312 3312 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2676
3313 3313 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2762
3314 3314 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2795
3315 3315 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2795
3316 3316 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3010
3317 3317 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3096
3318 3318 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3102
3319 3319 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3133
3320 3320 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2293
3321 3321 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2285
3322 3322 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2284
3323 3323 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2288
3324 3324 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2288
3325 3325 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2339
3326 3326 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2340
3327 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2540 rhodecode/model/db.py:3134
3327 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2540 rhodecode/model/db.py:3155
3328 3328 msgid "User Group creation disabled"
3329 3329 msgstr ""
3330 3330
3331 3331 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:675
3332 3332 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:677
3333 3333 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:700
3334 3334 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:701
3335 3335 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:718
3336 3336 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:735
3337 3337 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:751
3338 3338 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:754
3339 3339 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:756
3340 3340 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:756
3341 3341 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:783
3342 3342 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:793
3343 3343 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:834
3344 3344 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:834
3345 3345 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:835
3346 3346 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:835
3347 3347 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:841
3348 3348 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:963
3349 3349 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:988
3350 3350 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2606
3351 3351 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2677
3352 3352 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2763
3353 3353 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2796
3354 3354 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2796
3355 3355 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3011
3356 3356 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3097
3357 3357 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3103
3358 3358 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3134
3359 3359 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2294
3360 3360 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2286
3361 3361 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2285
3362 3362 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2289
3363 3363 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2289
3364 3364 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2340
3365 3365 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2341
3366 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2541 rhodecode/model/db.py:3135
3366 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2541 rhodecode/model/db.py:3156
3367 3367 msgid "User Group creation enabled"
3368 3368 msgstr ""
3369 3369
3370 3370 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:683
3371 3371 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:685
3372 3372 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:708
3373 3373 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:709
3374 3374 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:726
3375 3375 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:743
3376 3376 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:761
3377 3377 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:764
3378 3378 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:766
3379 3379 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:766
3380 3380 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:793
3381 3381 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:803
3382 3382 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:844
3383 3383 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:844
3384 3384 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:845
3385 3385 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:845
3386 3386 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:851
3387 3387 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:973
3388 3388 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:998
3389 3389 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2616
3390 3390 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2687
3391 3391 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2773
3392 3392 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2806
3393 3393 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2806
3394 3394 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3021
3395 3395 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3107
3396 3396 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3113
3397 3397 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3144
3398 3398 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2304
3399 3399 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2296
3400 3400 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2295
3401 3401 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2299
3402 3402 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2299
3403 3403 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2350
3404 3404 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2351
3405 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2551 rhodecode/model/db.py:3145
3405 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2551 rhodecode/model/db.py:3166
3406 3406 msgid "Registration disabled"
3407 3407 msgstr ""
3408 3408
3409 3409 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:684
3410 3410 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:686
3411 3411 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:709
3412 3412 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:710
3413 3413 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:727
3414 3414 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:744
3415 3415 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:762
3416 3416 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:765
3417 3417 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:767
3418 3418 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:767
3419 3419 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:794
3420 3420 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:804
3421 3421 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:845
3422 3422 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:845
3423 3423 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:846
3424 3424 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:846
3425 3425 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:852
3426 3426 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:974
3427 3427 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:999
3428 3428 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2617
3429 3429 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2688
3430 3430 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2774
3431 3431 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2807
3432 3432 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2807
3433 3433 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3022
3434 3434 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3108
3435 3435 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3114
3436 3436 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3145
3437 3437 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2305
3438 3438 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2297
3439 3439 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2296
3440 3440 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2300
3441 3441 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2300
3442 3442 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2351
3443 3443 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2352
3444 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2552 rhodecode/model/db.py:3146
3444 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2552 rhodecode/model/db.py:3167
3445 3445 msgid "User Registration with manual account activation"
3446 3446 msgstr ""
3447 3447
3448 3448 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:685
3449 3449 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:687
3450 3450 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:710
3451 3451 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:711
3452 3452 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:728
3453 3453 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:745
3454 3454 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:763
3455 3455 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:766
3456 3456 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:768
3457 3457 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:768
3458 3458 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:795
3459 3459 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:805
3460 3460 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:846
3461 3461 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:846
3462 3462 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:847
3463 3463 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:847
3464 3464 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:853
3465 3465 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:975
3466 3466 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1000
3467 3467 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2618
3468 3468 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2689
3469 3469 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2775
3470 3470 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2808
3471 3471 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2808
3472 3472 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3023
3473 3473 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3109
3474 3474 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3115
3475 3475 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3146
3476 3476 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2306
3477 3477 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2298
3478 3478 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2297
3479 3479 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2301
3480 3480 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2301
3481 3481 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2352
3482 3482 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2353
3483 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2553 rhodecode/model/db.py:3147
3483 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2553 rhodecode/model/db.py:3168
3484 3484 msgid "User Registration with automatic account activation"
3485 3485 msgstr ""
3486 3486
3487 3487 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:687
3488 3488 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:689
3489 3489 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:712
3490 3490 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:713
3491 3491 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:730
3492 3492 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:747
3493 3493 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:765
3494 3494 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:768
3495 3495 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:770
3496 3496 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:770
3497 3497 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:797
3498 3498 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:807
3499 3499 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:848
3500 3500 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:848
3501 3501 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:849
3502 3502 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:849
3503 3503 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:855
3504 3504 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:977
3505 3505 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1002
3506 3506 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2624
3507 3507 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2695
3508 3508 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2781
3509 3509 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2814
3510 3510 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2814
3511 3511 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3029
3512 3512 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3115
3513 3513 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3121
3514 3514 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3152
3515 3515 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2308
3516 3516 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2300
3517 3517 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2299
3518 3518 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2303
3519 3519 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2303
3520 3520 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2358
3521 3521 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2359
3522 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2559 rhodecode/model/db.py:3153
3522 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2559 rhodecode/model/db.py:3174
3523 3523 #: rhodecode/model/permission.py:105
3524 3524 msgid "Manual activation of external account"
3525 3525 msgstr ""
3526 3526
3527 3527 #: rhodecode/lib/dbmigrate/schema/db_1_7_0.py:688
3528 3528 #: rhodecode/lib/dbmigrate/schema/db_1_8_0.py:690
3529 3529 #: rhodecode/lib/dbmigrate/schema/db_2_0_0.py:713
3530 3530 #: rhodecode/lib/dbmigrate/schema/db_2_0_1.py:714
3531 3531 #: rhodecode/lib/dbmigrate/schema/db_2_0_2.py:731
3532 3532 #: rhodecode/lib/dbmigrate/schema/db_2_1_0.py:748
3533 3533 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:766
3534 3534 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:769
3535 3535 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:771
3536 3536 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:771
3537 3537 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:798
3538 3538 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:808
3539 3539 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:849
3540 3540 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:849
3541 3541 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:850
3542 3542 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:850
3543 3543 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:856
3544 3544 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:978
3545 3545 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1003
3546 3546 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2625
3547 3547 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2696
3548 3548 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2782
3549 3549 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2815
3550 3550 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2815
3551 3551 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3030
3552 3552 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3116
3553 3553 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3122
3554 3554 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3153
3555 3555 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2309
3556 3556 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2301
3557 3557 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2300
3558 3558 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2304
3559 3559 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2304
3560 3560 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2359
3561 3561 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2360
3562 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2560 rhodecode/model/db.py:3154
3562 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2560 rhodecode/model/db.py:3175
3563 3563 #: rhodecode/model/permission.py:106
3564 3564 msgid "Automatic activation of external account"
3565 3565 msgstr ""
3566 3566
3567 3567 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:755
3568 3568 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:758
3569 3569 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:760
3570 3570 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:760
3571 3571 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:787
3572 3572 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:797
3573 3573 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:838
3574 3574 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:838
3575 3575 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:839
3576 3576 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:839
3577 3577 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:845
3578 3578 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:967
3579 3579 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:992
3580 3580 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2610
3581 3581 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2681
3582 3582 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2767
3583 3583 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2800
3584 3584 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2800
3585 3585 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3015
3586 3586 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3101
3587 3587 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3107
3588 3588 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3138
3589 3589 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2298
3590 3590 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2290
3591 3591 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2289
3592 3592 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2293
3593 3593 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2293
3594 3594 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2344
3595 3595 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2345
3596 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2545 rhodecode/model/db.py:3139
3596 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2545 rhodecode/model/db.py:3160
3597 3597 msgid "Repository creation enabled with write permission to a repository group"
3598 3598 msgstr ""
3599 3599
3600 3600 #: rhodecode/lib/dbmigrate/schema/db_2_2_0.py:756
3601 3601 #: rhodecode/lib/dbmigrate/schema/db_2_2_3.py:759
3602 3602 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_0.py:761
3603 3603 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:761
3604 3604 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:788
3605 3605 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:798
3606 3606 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:839
3607 3607 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:839
3608 3608 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:840
3609 3609 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:840
3610 3610 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:846
3611 3611 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:968
3612 3612 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:993
3613 3613 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2611
3614 3614 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2682
3615 3615 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2768
3616 3616 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2801
3617 3617 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2801
3618 3618 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3016
3619 3619 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3102
3620 3620 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3108
3621 3621 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3139
3622 3622 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2299
3623 3623 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2291
3624 3624 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2290
3625 3625 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2294
3626 3626 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2294
3627 3627 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2345
3628 3628 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2346
3629 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2546 rhodecode/model/db.py:3140
3629 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2546 rhodecode/model/db.py:3161
3630 3630 msgid "Repository creation disabled with write permission to a repository group"
3631 3631 msgstr ""
3632 3632
3633 3633 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_1.py:735
3634 3634 #: rhodecode/lib/dbmigrate/schema/db_2_3_0_2.py:762
3635 3635 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:772
3636 3636 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:813
3637 3637 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:813
3638 3638 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:814
3639 3639 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:814
3640 3640 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:820
3641 3641 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:942
3642 3642 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:967
3643 3643 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2585
3644 3644 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2651
3645 3645 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2737
3646 3646 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2770
3647 3647 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2770
3648 3648 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2985
3649 3649 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3071
3650 3650 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3077
3651 3651 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3108
3652 3652 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2273
3653 3653 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2265
3654 3654 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2264
3655 3655 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2268
3656 3656 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2268
3657 3657 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2319
3658 3658 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2320
3659 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2520 rhodecode/model/db.py:3109
3659 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2520 rhodecode/model/db.py:3130
3660 3660 msgid "RhodeCode Super Administrator"
3661 3661 msgstr ""
3662 3662
3663 3663 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:810
3664 3664 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:851
3665 3665 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:851
3666 3666 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:852
3667 3667 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:852
3668 3668 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:858
3669 3669 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:980
3670 3670 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1005
3671 3671 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2627
3672 3672 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2698
3673 3673 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2784
3674 3674 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2817
3675 3675 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2817
3676 3676 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3032
3677 3677 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3118
3678 3678 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3124
3679 3679 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3155
3680 3680 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2311
3681 3681 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2303
3682 3682 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2302
3683 3683 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2306
3684 3684 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2306
3685 3685 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2361
3686 3686 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2362
3687 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2562 rhodecode/model/db.py:3156
3687 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2562 rhodecode/model/db.py:3177
3688 3688 msgid "Inherit object permissions from default user disabled"
3689 3689 msgstr ""
3690 3690
3691 3691 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_0.py:811
3692 3692 #: rhodecode/lib/dbmigrate/schema/db_3_0_0_1.py:852
3693 3693 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_0.py:852
3694 3694 #: rhodecode/lib/dbmigrate/schema/db_3_1_0_1.py:853
3695 3695 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:853
3696 3696 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:859
3697 3697 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:981
3698 3698 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1006
3699 3699 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2628
3700 3700 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2699
3701 3701 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2785
3702 3702 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2818
3703 3703 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2818
3704 3704 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3033
3705 3705 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3119
3706 3706 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3125
3707 3707 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3156
3708 3708 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2312
3709 3709 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2304
3710 3710 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2303
3711 3711 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2307
3712 3712 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2307
3713 3713 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2362
3714 3714 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2363
3715 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2563 rhodecode/model/db.py:3157
3715 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2563 rhodecode/model/db.py:3178
3716 3716 msgid "Inherit object permissions from default user enabled"
3717 3717 msgstr ""
3718 3718
3719 3719 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:1103
3720 3720 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:1107
3721 3721 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:1118
3722 3722 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:1120
3723 3723 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:1120
3724 3724 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:1137
3725 3725 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:1189
3726 3726 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:1195
3727 3727 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1202
3728 3728 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:910
3729 3729 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:911
3730 3730 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:910
3731 3731 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:912
3732 3732 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:912
3733 3733 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:955
3734 3734 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:956
3735 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1050 rhodecode/model/db.py:1203
3735 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1050 rhodecode/model/db.py:1224
3736 3736 msgid "all"
3737 3737 msgstr ""
3738 3738
3739 3739 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:1104
3740 3740 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:1108
3741 3741 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:1119
3742 3742 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:1121
3743 3743 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:1121
3744 3744 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:1138
3745 3745 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:1190
3746 3746 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:1196
3747 3747 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1203
3748 3748 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:911
3749 3749 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:912
3750 3750 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:911
3751 3751 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:913
3752 3752 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:913
3753 3753 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:956
3754 3754 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:957
3755 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1051 rhodecode/model/db.py:1204
3755 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1051 rhodecode/model/db.py:1225
3756 3756 msgid "http/web interface"
3757 3757 msgstr ""
3758 3758
3759 3759 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:1105
3760 3760 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:1109
3761 3761 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:1120
3762 3762 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:1122
3763 3763 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:1122
3764 3764 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:1139
3765 3765 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:1191
3766 3766 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:1197
3767 3767 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1204
3768 3768 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:912
3769 3769 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:913
3770 3770 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:912
3771 3771 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:914
3772 3772 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:914
3773 3773 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:957
3774 3774 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:958
3775 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1052 rhodecode/model/db.py:1205
3775 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1052 rhodecode/model/db.py:1226
3776 3776 msgid "vcs (git/hg/svn protocol)"
3777 3777 msgstr ""
3778 3778
3779 3779 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:1106
3780 3780 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:1110
3781 3781 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:1121
3782 3782 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:1123
3783 3783 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:1123
3784 3784 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:1140
3785 3785 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:1192
3786 3786 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:1198
3787 3787 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1205
3788 3788 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:913
3789 3789 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:914
3790 3790 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:913
3791 3791 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:915
3792 3792 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:915
3793 3793 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:958
3794 3794 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:959
3795 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1053 rhodecode/model/db.py:1206
3795 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1053 rhodecode/model/db.py:1227
3796 3796 msgid "api calls"
3797 3797 msgstr ""
3798 3798
3799 3799 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:1107
3800 3800 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:1111
3801 3801 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:1122
3802 3802 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:1124
3803 3803 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:1124
3804 3804 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:1141
3805 3805 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:1193
3806 3806 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:1199
3807 3807 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1206
3808 3808 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:914
3809 3809 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:915
3810 3810 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:914
3811 3811 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:916
3812 3812 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:916
3813 3813 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:959
3814 3814 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:960
3815 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1054 rhodecode/model/db.py:1207
3815 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:1054 rhodecode/model/db.py:1228
3816 3816 msgid "feed access"
3817 3817 msgstr ""
3818 3818
3819 3819 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2348
3820 3820 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2414
3821 3821 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2488
3822 3822 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2511
3823 3823 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2511
3824 3824 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:2638
3825 3825 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:2729
3826 3826 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:2735
3827 3827 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:2766
3828 3828 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2051
3829 3829 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_0.py:2043
3830 3830 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_1.py:2042
3831 3831 #: rhodecode/lib/dbmigrate/schema/db_4_4_0_2.py:2046
3832 3832 #: rhodecode/lib/dbmigrate/schema/db_4_5_0_0.py:2046
3833 3833 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2090
3834 3834 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2091
3835 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2284 rhodecode/model/db.py:2767
3835 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2284 rhodecode/model/db.py:2788
3836 3836 msgid "No parent"
3837 3837 msgstr ""
3838 3838
3839 3839 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2620
3840 3840 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2691
3841 3841 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2777
3842 3842 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2810
3843 3843 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2810
3844 3844 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3025
3845 3845 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3111
3846 3846 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3117
3847 3847 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3148
3848 3848 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2354
3849 3849 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2355
3850 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2555 rhodecode/model/db.py:3149
3850 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2555 rhodecode/model/db.py:3170
3851 3851 msgid "Password reset enabled"
3852 3852 msgstr ""
3853 3853
3854 3854 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2621
3855 3855 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2692
3856 3856 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2778
3857 3857 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2811
3858 3858 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2811
3859 3859 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3026
3860 3860 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3112
3861 3861 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3118
3862 3862 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3149
3863 3863 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2355
3864 3864 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2356
3865 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2556 rhodecode/model/db.py:3150
3865 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2556 rhodecode/model/db.py:3171
3866 3866 msgid "Password reset hidden"
3867 3867 msgstr ""
3868 3868
3869 3869 #: rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py:2622
3870 3870 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2693
3871 3871 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2779
3872 3872 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2812
3873 3873 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2812
3874 3874 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3027
3875 3875 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3113
3876 3876 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3119
3877 3877 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3150
3878 3878 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_0.py:2356
3879 3879 #: rhodecode/lib/dbmigrate/schema/db_4_7_0_1.py:2357
3880 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2557 rhodecode/model/db.py:3151
3880 #: rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py:2557 rhodecode/model/db.py:3172
3881 3881 msgid "Password reset disabled"
3882 3882 msgstr ""
3883 3883
3884 3884 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2668
3885 3885 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2754
3886 3886 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2787
3887 3887 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2787
3888 3888 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3002
3889 3889 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3088
3890 3890 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3094
3891 3891 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3125
3892 #: rhodecode/model/db.py:3126
3892 #: rhodecode/model/db.py:3147
3893 3893 msgid "Branch no permissions"
3894 3894 msgstr ""
3895 3895
3896 3896 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2669
3897 3897 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2755
3898 3898 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2788
3899 3899 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2788
3900 3900 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3003
3901 3901 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3089
3902 3902 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3095
3903 3903 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3126
3904 #: rhodecode/model/db.py:3127
3904 #: rhodecode/model/db.py:3148
3905 3905 msgid "Branch access by web merge"
3906 3906 msgstr ""
3907 3907
3908 3908 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2670
3909 3909 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2756
3910 3910 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2789
3911 3911 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2789
3912 3912 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3004
3913 3913 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3090
3914 3914 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3096
3915 3915 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3127
3916 #: rhodecode/model/db.py:3128
3916 #: rhodecode/model/db.py:3149
3917 3917 msgid "Branch access by push"
3918 3918 msgstr ""
3919 3919
3920 3920 #: rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py:2671
3921 3921 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_0.py:2757
3922 3922 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_1.py:2790
3923 3923 #: rhodecode/lib/dbmigrate/schema/db_4_16_0_2.py:2790
3924 3924 #: rhodecode/lib/dbmigrate/schema/db_4_18_0_1.py:3005
3925 3925 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:3091
3926 3926 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:3097
3927 3927 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:3128
3928 #: rhodecode/model/db.py:3129
3928 #: rhodecode/model/db.py:3150
3929 3929 msgid "Branch access by push with force"
3930 3930 msgstr ""
3931 3931
3932 3932 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_0.py:1194
3933 3933 #: rhodecode/lib/dbmigrate/schema/db_4_19_0_2.py:1200
3934 3934 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1207
3935 #: rhodecode/model/db.py:1208
3935 #: rhodecode/model/db.py:1229
3936 3936 msgid "artifacts downloads"
3937 3937 msgstr ""
3938 3938
3939 3939 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1213
3940 #: rhodecode/model/db.py:1214
3940 #: rhodecode/model/db.py:1235
3941 3941 msgid "Token for all actions."
3942 3942 msgstr ""
3943 3943
3944 3944 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1214
3945 #: rhodecode/model/db.py:1215
3945 #: rhodecode/model/db.py:1236
3946 3946 msgid "Token to access RhodeCode pages via web interface without login using `api_access_controllers_whitelist` functionality."
3947 3947 msgstr ""
3948 3948
3949 3949 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1216
3950 #: rhodecode/model/db.py:1217
3950 #: rhodecode/model/db.py:1238
3951 3951 msgid "Token to interact over git/hg/svn protocols. Requires auth_token authentication plugin to be active. <br/>Such Token should be used then instead of a password to interact with a repository, and additionally can be limited to single repository using repo scope."
3952 3952 msgstr ""
3953 3953
3954 3954 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1221
3955 #: rhodecode/model/db.py:1222
3955 #: rhodecode/model/db.py:1243
3956 3956 msgid "Token limited to api calls."
3957 3957 msgstr ""
3958 3958
3959 3959 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1222
3960 #: rhodecode/model/db.py:1223
3960 #: rhodecode/model/db.py:1244
3961 3961 msgid "Token to read RSS/ATOM feed."
3962 3962 msgstr ""
3963 3963
3964 3964 #: rhodecode/lib/dbmigrate/schema/db_4_20_0_0.py:1223
3965 #: rhodecode/model/db.py:1224
3965 #: rhodecode/model/db.py:1245
3966 3966 msgid "Token for artifacts downloads."
3967 3967 msgstr ""
3968 3968
3969 3969 #: rhodecode/lib/index/whoosh.py:189
3970 3970 msgid "Index Type"
3971 3971 msgstr ""
3972 3972
3973 3973 #: rhodecode/lib/index/whoosh.py:192
3974 3974 msgid "File Index"
3975 3975 msgstr ""
3976 3976
3977 3977 #: rhodecode/lib/index/whoosh.py:193 rhodecode/lib/index/whoosh.py:199
3978 3978 msgid "Indexed documents"
3979 3979 msgstr ""
3980 3980
3981 3981 #: rhodecode/lib/index/whoosh.py:194 rhodecode/lib/index/whoosh.py:200
3982 3982 msgid "Last update"
3983 3983 msgstr ""
3984 3984
3985 3985 #: rhodecode/lib/index/whoosh.py:198
3986 3986 msgid "Commit index"
3987 3987 msgstr ""
3988 3988
3989 3989 #: rhodecode/lib/vcs/backends/base.py:186
3990 3990 msgid "This pull request can be automatically merged."
3991 3991 msgstr ""
3992 3992
3993 3993 #: rhodecode/lib/vcs/backends/base.py:188
3994 3994 msgid "This pull request cannot be merged because of an unhandled exception. {exception}"
3995 3995 msgstr ""
3996 3996
3997 3997 #: rhodecode/lib/vcs/backends/base.py:191
3998 3998 msgid "This pull request cannot be merged because of merge conflicts. {unresolved_files}"
3999 3999 msgstr ""
4000 4000
4001 4001 #: rhodecode/lib/vcs/backends/base.py:193
4002 4002 msgid "This pull request could not be merged because push to target:`{target}@{merge_commit}` failed."
4003 4003 msgstr ""
4004 4004
4005 4005 #: rhodecode/lib/vcs/backends/base.py:196
4006 4006 msgid "This pull request cannot be merged because the target `{target_ref.name}` is not a head."
4007 4007 msgstr ""
4008 4008
4009 4009 #: rhodecode/lib/vcs/backends/base.py:199
4010 4010 msgid "This pull request cannot be merged because the source contains more branches than the target."
4011 4011 msgstr ""
4012 4012
4013 4013 #: rhodecode/lib/vcs/backends/base.py:202
4014 4014 msgid "This pull request cannot be merged because the target `{target_ref.name}` has multiple heads: `{heads}`."
4015 4015 msgstr ""
4016 4016
4017 4017 #: rhodecode/lib/vcs/backends/base.py:205
4018 4018 msgid "This pull request cannot be merged because the target repository is locked by {locked_by}."
4019 4019 msgstr ""
4020 4020
4021 4021 #: rhodecode/lib/vcs/backends/base.py:209
4022 4022 msgid "This pull request cannot be merged because the target reference `{target_ref.name}` is missing."
4023 4023 msgstr ""
4024 4024
4025 4025 #: rhodecode/lib/vcs/backends/base.py:212
4026 4026 msgid "This pull request cannot be merged because the source reference `{source_ref.name}` is missing."
4027 4027 msgstr ""
4028 4028
4029 4029 #: rhodecode/lib/vcs/backends/base.py:215
4030 4030 msgid "This pull request cannot be merged because of conflicts related to sub repositories."
4031 4031 msgstr ""
4032 4032
4033 4033 #: rhodecode/lib/vcs/backends/base.py:220
4034 4034 msgid "This pull request cannot be merged because the target or the source reference is missing."
4035 4035 msgstr ""
4036 4036
4037 4037 #: rhodecode/model/auth_token.py:53
4038 4038 msgid "5 minutes {end_date}"
4039 4039 msgstr ""
4040 4040
4041 4041 #: rhodecode/model/auth_token.py:55
4042 4042 msgid "1 hour {end_date}"
4043 4043 msgstr ""
4044 4044
4045 4045 #: rhodecode/model/auth_token.py:57
4046 4046 msgid "1 day {end_date}"
4047 4047 msgstr ""
4048 4048
4049 4049 #: rhodecode/model/auth_token.py:59
4050 4050 msgid "1 month {end_date}"
4051 4051 msgstr ""
4052 4052
4053 4053 #: rhodecode/model/forms.py:88
4054 4054 msgid "Please enter a login"
4055 4055 msgstr ""
4056 4056
4057 4057 #: rhodecode/model/forms.py:89
4058 4058 #, python-format
4059 4059 msgid "Enter a value %(min)i characters long or more"
4060 4060 msgstr ""
4061 4061
4062 4062 #: rhodecode/model/forms.py:99
4063 4063 msgid "Please enter a password"
4064 4064 msgstr ""
4065 4065
4066 4066 #: rhodecode/model/forms.py:100
4067 4067 #, python-format
4068 4068 msgid "Enter %(min)i characters or more"
4069 4069 msgstr ""
4070 4070
4071 4071 #: rhodecode/model/notification.py:263
4072 4072 #, python-format
4073 4073 msgid "%(user)s commented on commit %(date_or_age)s"
4074 4074 msgstr ""
4075 4075
4076 4076 #: rhodecode/model/notification.py:264
4077 4077 #, python-format
4078 4078 msgid "%(user)s commented on commit at %(date_or_age)s"
4079 4079 msgstr ""
4080 4080
4081 4081 #: rhodecode/model/notification.py:267
4082 4082 #, python-format
4083 4083 msgid "%(user)s sent message %(date_or_age)s"
4084 4084 msgstr ""
4085 4085
4086 4086 #: rhodecode/model/notification.py:268
4087 4087 #, python-format
4088 4088 msgid "%(user)s sent message at %(date_or_age)s"
4089 4089 msgstr ""
4090 4090
4091 4091 #: rhodecode/model/notification.py:271
4092 4092 #, python-format
4093 4093 msgid "%(user)s mentioned you %(date_or_age)s"
4094 4094 msgstr ""
4095 4095
4096 4096 #: rhodecode/model/notification.py:272
4097 4097 #, python-format
4098 4098 msgid "%(user)s mentioned you at %(date_or_age)s"
4099 4099 msgstr ""
4100 4100
4101 4101 #: rhodecode/model/notification.py:275
4102 4102 #, python-format
4103 4103 msgid "%(user)s registered in RhodeCode %(date_or_age)s"
4104 4104 msgstr ""
4105 4105
4106 4106 #: rhodecode/model/notification.py:276
4107 4107 #, python-format
4108 4108 msgid "%(user)s registered in RhodeCode at %(date_or_age)s"
4109 4109 msgstr ""
4110 4110
4111 4111 #: rhodecode/model/notification.py:279
4112 4112 #, python-format
4113 4113 msgid "%(user)s opened new pull request %(date_or_age)s"
4114 4114 msgstr ""
4115 4115
4116 4116 #: rhodecode/model/notification.py:280
4117 4117 #, python-format
4118 4118 msgid "%(user)s opened new pull request at %(date_or_age)s"
4119 4119 msgstr ""
4120 4120
4121 4121 #: rhodecode/model/notification.py:283
4122 4122 #, python-format
4123 4123 msgid "%(user)s updated pull request %(date_or_age)s"
4124 4124 msgstr ""
4125 4125
4126 4126 #: rhodecode/model/notification.py:284
4127 4127 #, python-format
4128 4128 msgid "%(user)s updated pull request at %(date_or_age)s"
4129 4129 msgstr ""
4130 4130
4131 4131 #: rhodecode/model/notification.py:287
4132 4132 #, python-format
4133 4133 msgid "%(user)s commented on pull request %(date_or_age)s"
4134 4134 msgstr ""
4135 4135
4136 4136 #: rhodecode/model/notification.py:288
4137 4137 #, python-format
4138 4138 msgid "%(user)s commented on pull request at %(date_or_age)s"
4139 4139 msgstr ""
4140 4140
4141 4141 #: rhodecode/model/permission.py:71 rhodecode/model/permission.py:77
4142 4142 #: rhodecode/model/permission.py:83
4143 4143 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:11
4144 4144 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:217
4145 4145 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:11
4146 4146 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:15
4147 4147 msgid "None"
4148 4148 msgstr ""
4149 4149
4150 4150 #: rhodecode/model/permission.py:72 rhodecode/model/permission.py:78
4151 4151 #: rhodecode/model/permission.py:84
4152 4152 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:12
4153 4153 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:12
4154 4154 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:16
4155 4155 msgid "Read"
4156 4156 msgstr ""
4157 4157
4158 4158 #: rhodecode/model/permission.py:73 rhodecode/model/permission.py:79
4159 4159 #: rhodecode/model/permission.py:85
4160 4160 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:13
4161 4161 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:13
4162 4162 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:17
4163 4163 #: rhodecode/templates/changeset/changeset_file_comment.mako:392
4164 4164 #: rhodecode/templates/changeset/changeset_file_comment.mako:443
4165 4165 #: rhodecode/templates/data_table/_dt_elements.mako:453
4166 4166 msgid "Write"
4167 4167 msgstr ""
4168 4168
4169 4169 #: rhodecode/model/permission.py:74 rhodecode/model/permission.py:80
4170 4170 #: rhodecode/model/permission.py:86
4171 4171 #: rhodecode/templates/admin/auth/plugin_settings.mako:12
4172 4172 #: rhodecode/templates/admin/defaults/defaults.mako:12
4173 4173 #: rhodecode/templates/admin/integrations/base.mako:21
4174 4174 #: rhodecode/templates/admin/integrations/form.mako:15
4175 4175 #: rhodecode/templates/admin/integrations/form.mako:28
4176 4176 #: rhodecode/templates/admin/integrations/global.mako:12
4177 4177 #: rhodecode/templates/admin/integrations/list.mako:8
4178 4178 #: rhodecode/templates/admin/integrations/list.mako:14
4179 4179 #: rhodecode/templates/admin/integrations/new.mako:11
4180 4180 #: rhodecode/templates/admin/permissions/permissions.mako:12
4181 4181 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:12
4182 4182 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:14
4183 4183 #: rhodecode/templates/admin/repos/repo_add.mako:13
4184 4184 #: rhodecode/templates/admin/repos/repo_add.mako:17
4185 4185 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:14
4186 4186 #: rhodecode/templates/admin/settings/settings.mako:12
4187 4187 #: rhodecode/templates/admin/user_groups/user_group_add.mako:11
4188 4188 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:12
4189 4189 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:18
4190 4190 #: rhodecode/templates/admin/users/user_add.mako:11
4191 4191 #: rhodecode/templates/admin/users/user_edit.mako:12
4192 4192 #: rhodecode/templates/base/base.mako:838
4193 4193 msgid "Admin"
4194 4194 msgstr ""
4195 4195
4196 4196 #: rhodecode/model/permission.py:89
4197 4197 msgid "Protected/No Access"
4198 4198 msgstr ""
4199 4199
4200 4200 #: rhodecode/model/permission.py:90
4201 4201 msgid "Web merge"
4202 4202 msgstr ""
4203 4203
4204 4204 #: rhodecode/model/permission.py:91
4205 4205 msgid "Push"
4206 4206 msgstr ""
4207 4207
4208 4208 #: rhodecode/model/permission.py:92
4209 4209 msgid "Force Push"
4210 4210 msgstr ""
4211 4211
4212 4212 #: rhodecode/model/permission.py:95 rhodecode/model/permission.py:109
4213 4213 #: rhodecode/model/permission.py:113 rhodecode/model/permission.py:117
4214 4214 #: rhodecode/model/permission.py:121 rhodecode/model/permission.py:125
4215 4215 #: rhodecode/model/permission.py:129
4216 4216 #: rhodecode/templates/admin/my_account/my_account_notifications.mako:27
4217 4217 msgid "Disabled"
4218 4218 msgstr ""
4219 4219
4220 4220 #: rhodecode/model/permission.py:96
4221 4221 msgid "Allowed with manual account activation"
4222 4222 msgstr ""
4223 4223
4224 4224 #: rhodecode/model/permission.py:97
4225 4225 msgid "Allowed with automatic account activation"
4226 4226 msgstr ""
4227 4227
4228 4228 #: rhodecode/model/permission.py:100
4229 4229 msgid "Allow password recovery"
4230 4230 msgstr ""
4231 4231
4232 4232 #: rhodecode/model/permission.py:101
4233 4233 msgid "Hide password recovery link"
4234 4234 msgstr ""
4235 4235
4236 4236 #: rhodecode/model/permission.py:102
4237 4237 msgid "Disable password recovery"
4238 4238 msgstr ""
4239 4239
4240 4240 #: rhodecode/model/pull_request.py:245
4241 4241 msgid "Pull request update successful."
4242 4242 msgstr ""
4243 4243
4244 4244 #: rhodecode/model/pull_request.py:247
4245 4245 msgid "Pull request update failed because of an unknown error."
4246 4246 msgstr ""
4247 4247
4248 4248 #: rhodecode/model/pull_request.py:249
4249 4249 msgid "No update needed because the source and target have not changed."
4250 4250 msgstr ""
4251 4251
4252 4252 #: rhodecode/model/pull_request.py:251
4253 4253 msgid "Pull request cannot be updated because the reference type is not supported for an update. Only Branch, Tag or Bookmark is allowed."
4254 4254 msgstr ""
4255 4255
4256 4256 #: rhodecode/model/pull_request.py:254
4257 4257 msgid "This pull request cannot be updated because the target reference is missing."
4258 4258 msgstr ""
4259 4259
4260 4260 #: rhodecode/model/pull_request.py:257
4261 4261 msgid "This pull request cannot be updated because the source reference is missing."
4262 4262 msgstr ""
4263 4263
4264 #: rhodecode/model/pull_request.py:1674
4264 #: rhodecode/model/pull_request.py:1688
4265 4265 msgid "Server-side pull request merging is disabled."
4266 4266 msgstr ""
4267 4267
4268 #: rhodecode/model/pull_request.py:1677
4269 msgid "This pull request is closed."
4270 msgstr ""
4271
4272 4268 #: rhodecode/model/pull_request.py:1691
4269 msgid "This pull request is closed."
4270 msgstr ""
4271
4272 #: rhodecode/model/pull_request.py:1705
4273 4273 msgid "Pull request merging is not supported."
4274 4274 msgstr ""
4275 4275
4276 #: rhodecode/model/pull_request.py:1708
4276 #: rhodecode/model/pull_request.py:1722
4277 4277 msgid "Target repository large files support is disabled."
4278 4278 msgstr ""
4279 4279
4280 #: rhodecode/model/pull_request.py:1711
4280 #: rhodecode/model/pull_request.py:1725
4281 4281 msgid "Source repository large files support is disabled."
4282 4282 msgstr ""
4283 4283
4284 #: rhodecode/model/pull_request.py:1895 rhodecode/model/scm.py:1008
4284 #: rhodecode/model/pull_request.py:1909 rhodecode/model/scm.py:1008
4285 4285 #: rhodecode/templates/admin/my_account/my_account.mako:32
4286 4286 #: rhodecode/templates/base/base.mako:638
4287 4287 #: rhodecode/templates/summary/components.mako:46
4288 4288 msgid "Bookmarks"
4289 4289 msgstr ""
4290 4290
4291 #: rhodecode/model/pull_request.py:1900
4291 #: rhodecode/model/pull_request.py:1914
4292 4292 msgid "Commit IDs"
4293 4293 msgstr ""
4294 4294
4295 #: rhodecode/model/pull_request.py:1903
4295 #: rhodecode/model/pull_request.py:1917
4296 4296 #: rhodecode/templates/summary/components.mako:22
4297 4297 msgid "Closed Branches"
4298 4298 msgstr ""
4299 4299
4300 #: rhodecode/model/pull_request.py:2089
4300 #: rhodecode/model/pull_request.py:2103
4301 4301 msgid "WIP marker in title prevents from accidental merge."
4302 4302 msgstr ""
4303 4303
4304 #: rhodecode/model/pull_request.py:2099
4304 #: rhodecode/model/pull_request.py:2113
4305 4305 msgid "User `{}` not allowed to perform merge."
4306 4306 msgstr ""
4307 4307
4308 #: rhodecode/model/pull_request.py:2117
4308 #: rhodecode/model/pull_request.py:2131
4309 4309 msgid "Target branch `{}` changes rejected by rule {}."
4310 4310 msgstr ""
4311 4311
4312 #: rhodecode/model/pull_request.py:2132
4313 msgid "Pull request reviewer approval is pending."
4314 msgstr ""
4315
4316 4312 #: rhodecode/model/pull_request.py:2146
4313 msgid "Pull request reviewer approval is pending."
4314 msgstr ""
4315
4316 #: rhodecode/model/pull_request.py:2160
4317 4317 msgid "Cannot merge, {} TODO still not resolved."
4318 4318 msgstr ""
4319 4319
4320 #: rhodecode/model/pull_request.py:2149
4320 #: rhodecode/model/pull_request.py:2163
4321 4321 msgid "Cannot merge, {} TODOs still not resolved."
4322 4322 msgstr ""
4323 4323
4324 #: rhodecode/model/pull_request.py:2204
4324 #: rhodecode/model/pull_request.py:2218
4325 4325 msgid "Merge strategy: rebase"
4326 4326 msgstr ""
4327 4327
4328 #: rhodecode/model/pull_request.py:2209
4328 #: rhodecode/model/pull_request.py:2223
4329 4329 msgid "Merge strategy: explicit merge commit"
4330 4330 msgstr ""
4331 4331
4332 #: rhodecode/model/pull_request.py:2217
4332 #: rhodecode/model/pull_request.py:2231
4333 4333 msgid "Source branch will be closed before the merge."
4334 4334 msgstr ""
4335 4335
4336 #: rhodecode/model/pull_request.py:2219
4336 #: rhodecode/model/pull_request.py:2233
4337 4337 msgid "Source branch will be deleted after the merge."
4338 4338 msgstr ""
4339 4339
4340 4340 #: rhodecode/model/validators.py:93 rhodecode/model/validators.py:94
4341 4341 msgid "Value cannot be an empty list"
4342 4342 msgstr ""
4343 4343
4344 4344 #: rhodecode/model/validators.py:140
4345 4345 msgid "Pattern already exists"
4346 4346 msgstr ""
4347 4347
4348 4348 #: rhodecode/model/validators.py:161
4349 4349 #, python-format
4350 4350 msgid "Username \"%(username)s\" already exists"
4351 4351 msgstr ""
4352 4352
4353 4353 #: rhodecode/model/validators.py:163
4354 4354 #, python-format
4355 4355 msgid "Username \"%(username)s\" is forbidden"
4356 4356 msgstr ""
4357 4357
4358 4358 #: rhodecode/model/validators.py:165
4359 4359 #: rhodecode/model/validation_schema/schemas/user_schema.py:71
4360 4360 msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character or underscore"
4361 4361 msgstr ""
4362 4362
4363 4363 #: rhodecode/model/validators.py:196
4364 4364 #, python-format
4365 4365 msgid "Username %(username)s is not valid"
4366 4366 msgstr ""
4367 4367
4368 4368 #: rhodecode/model/validators.py:197
4369 4369 #, python-format
4370 4370 msgid "Username %(username)s is disabled"
4371 4371 msgstr ""
4372 4372
4373 4373 #: rhodecode/model/validators.py:222
4374 4374 msgid "Invalid user group name"
4375 4375 msgstr ""
4376 4376
4377 4377 #: rhodecode/model/validators.py:223
4378 4378 #, python-format
4379 4379 msgid "User group `%(usergroup)s` already exists"
4380 4380 msgstr ""
4381 4381
4382 4382 #: rhodecode/model/validators.py:225
4383 4383 msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
4384 4384 msgstr ""
4385 4385
4386 4386 #: rhodecode/model/validators.py:265
4387 4387 msgid "Cannot assign this group as parent"
4388 4388 msgstr ""
4389 4389
4390 4390 #: rhodecode/model/validators.py:266
4391 4391 #, python-format
4392 4392 msgid "Group \"%(group_name)s\" already exists"
4393 4393 msgstr ""
4394 4394
4395 4395 #: rhodecode/model/validators.py:267
4396 4396 #, python-format
4397 4397 msgid "Repository with name \"%(group_name)s\" already exists"
4398 4398 msgstr ""
4399 4399
4400 4400 #: rhodecode/model/validators.py:269
4401 4401 msgid "no permission to store repository groupin this location"
4402 4402 msgstr ""
4403 4403
4404 4404 #: rhodecode/model/validators.py:271
4405 4405 msgid "no permission to store repository group in root location"
4406 4406 msgstr ""
4407 4407
4408 4408 #: rhodecode/model/validators.py:388
4409 4409 msgid "Invalid characters (non-ascii) in password"
4410 4410 msgstr ""
4411 4411
4412 4412 #: rhodecode/model/validators.py:407
4413 4413 msgid "Passwords do not match"
4414 4414 msgstr ""
4415 4415
4416 4416 #: rhodecode/model/validators.py:427
4417 4417 msgid "invalid password"
4418 4418 msgstr ""
4419 4419
4420 4420 #: rhodecode/model/validators.py:428
4421 4421 msgid "invalid user name"
4422 4422 msgstr ""
4423 4423
4424 4424 #: rhodecode/model/validators.py:429
4425 4425 msgid "Your account is disabled"
4426 4426 msgstr ""
4427 4427
4428 4428 #: rhodecode/model/validators.py:465
4429 4429 #, python-format
4430 4430 msgid "Repository name %(repo)s is disallowed"
4431 4431 msgstr ""
4432 4432
4433 4433 #: rhodecode/model/validators.py:467
4434 4434 #, python-format
4435 4435 msgid "Repository with name %(repo)s already exists"
4436 4436 msgstr ""
4437 4437
4438 4438 #: rhodecode/model/validators.py:469
4439 4439 #, python-format
4440 4440 msgid "Repository group with name \"%(repo)s\" already exists"
4441 4441 msgstr ""
4442 4442
4443 4443 #: rhodecode/model/validators.py:472
4444 4444 #, python-format
4445 4445 msgid "Repository with name %(repo)s exists in group \"%(group)s\""
4446 4446 msgstr ""
4447 4447
4448 4448 #: rhodecode/model/validators.py:474
4449 4449 #, python-format
4450 4450 msgid "Repository group with name \"%(repo)s\" exists in group \"%(group)s\""
4451 4451 msgstr ""
4452 4452
4453 4453 #: rhodecode/model/validators.py:567
4454 4454 #: rhodecode/model/validation_schema/schemas/repo_schema.py:236
4455 4455 msgid "Repository name cannot end with .git"
4456 4456 msgstr ""
4457 4457
4458 4458 #: rhodecode/model/validators.py:627
4459 4459 #, python-format
4460 4460 msgid "invalid clone url for %(rtype)s repository"
4461 4461 msgstr ""
4462 4462
4463 4463 #: rhodecode/model/validators.py:628
4464 4464 #, python-format
4465 4465 msgid "Invalid clone url, provide a valid clone url starting with one of %(allowed_prefixes)s"
4466 4466 msgstr ""
4467 4467
4468 4468 #: rhodecode/model/validators.py:660
4469 4469 msgid "Fork have to be the same type as parent"
4470 4470 msgstr ""
4471 4471
4472 4472 #: rhodecode/model/validators.py:677
4473 4473 msgid "You do not have the permission to create repositories in this group."
4474 4474 msgstr ""
4475 4475
4476 4476 #: rhodecode/model/validators.py:680
4477 4477 #: rhodecode/model/validation_schema/schemas/repo_schema.py:136
4478 4478 msgid "You do not have the permission to store repositories in the root location."
4479 4479 msgstr ""
4480 4480
4481 4481 #: rhodecode/model/validators.py:740
4482 4482 msgid "This username or user group name is not valid"
4483 4483 msgstr ""
4484 4484
4485 4485 #: rhodecode/model/validators.py:853
4486 4486 msgid "This is not a valid path"
4487 4487 msgstr ""
4488 4488
4489 4489 #: rhodecode/model/validators.py:871
4490 4490 #: rhodecode/model/validation_schema/schemas/user_schema.py:144
4491 4491 #: rhodecode/model/validation_schema/schemas/user_schema.py:148
4492 4492 msgid "This e-mail address is already taken"
4493 4493 msgstr ""
4494 4494
4495 4495 #: rhodecode/model/validators.py:893
4496 4496 #, python-format
4497 4497 msgid "e-mail \"%(email)s\" does not exist."
4498 4498 msgstr ""
4499 4499
4500 4500 #: rhodecode/model/validators.py:914
4501 4501 #, python-format
4502 4502 msgid "Revisions %(revs)s are already part of pull request or have set status"
4503 4503 msgstr ""
4504 4504
4505 4505 #: rhodecode/model/validators.py:947
4506 4506 #: rhodecode/model/validation_schema/validators.py:41
4507 4507 #: rhodecode/model/validation_schema/validators.py:54
4508 4508 msgid "Please enter a valid IPv4 or IpV6 address"
4509 4509 msgstr ""
4510 4510
4511 4511 #: rhodecode/model/validators.py:948
4512 4512 #, python-format
4513 4513 msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
4514 4514 msgstr ""
4515 4515
4516 4516 #: rhodecode/model/validators.py:976
4517 4517 msgid "Key name can only consist of letters, underscore, dash or numbers"
4518 4518 msgstr ""
4519 4519
4520 4520 #: rhodecode/model/validators.py:993
4521 4521 #, python-format
4522 4522 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
4523 4523 msgstr ""
4524 4524
4525 4525 #: rhodecode/model/validators.py:996
4526 4526 #, python-format
4527 4527 msgid "The plugin \"%(plugin_id)s\" is missing an includeme function."
4528 4528 msgstr ""
4529 4529
4530 4530 #: rhodecode/model/validators.py:999
4531 4531 #, python-format
4532 4532 msgid "Can not load plugin \"%(plugin_id)s\""
4533 4533 msgstr ""
4534 4534
4535 4535 #: rhodecode/model/validators.py:1001
4536 4536 #, python-format
4537 4537 msgid "No plugin available with ID \"%(plugin_id)s\""
4538 4538 msgstr ""
4539 4539
4540 4540 #: rhodecode/model/validators.py:1069
4541 4541 msgid "Url must start with http or /"
4542 4542 msgstr ""
4543 4543
4544 4544 #: rhodecode/model/validation_schema/validators.py:62
4545 4545 msgid "Invalid glob pattern"
4546 4546 msgstr ""
4547 4547
4548 4548 #: rhodecode/model/validation_schema/validators.py:71
4549 4549 msgid "Name must start with a letter or number. Got `{}`"
4550 4550 msgstr ""
4551 4551
4552 4552 #: rhodecode/model/validation_schema/validators.py:142
4553 4553 msgid "invalid clone url for {repo_type} repository"
4554 4554 msgstr ""
4555 4555
4556 4556 #: rhodecode/model/validation_schema/validators.py:151
4557 4557 msgid "Please enter a valid json object"
4558 4558 msgstr ""
4559 4559
4560 #: rhodecode/model/validation_schema/validators.py:159
4561 msgid "Please enter a valid json object: `{}`"
4562 msgstr ""
4563
4560 4564 #: rhodecode/model/validation_schema/schemas/comment_schema.py:42
4561 4565 #: rhodecode/model/validation_schema/schemas/gist_schema.py:89
4562 4566 msgid "Gist with name {} already exists"
4563 4567 msgstr ""
4564 4568
4565 4569 #: rhodecode/model/validation_schema/schemas/comment_schema.py:48
4566 4570 #: rhodecode/model/validation_schema/schemas/gist_schema.py:95
4567 4571 msgid "Filename {} cannot be inside a directory"
4568 4572 msgstr ""
4569 4573
4570 4574 #: rhodecode/model/validation_schema/schemas/gist_schema.py:132
4571 4575 msgid "Duplicated value for filename found: `{}`"
4572 4576 msgstr ""
4573 4577
4574 4578 #: rhodecode/model/validation_schema/schemas/integration_schema.py:36
4575 4579 msgid "Pick a scope:"
4576 4580 msgstr ""
4577 4581
4578 4582 #: rhodecode/model/validation_schema/schemas/integration_schema.py:39
4579 4583 msgid "Global (all repositories)"
4580 4584 msgstr ""
4581 4585
4582 4586 #: rhodecode/model/validation_schema/schemas/integration_schema.py:40
4583 4587 msgid "Top level repositories only"
4584 4588 msgstr ""
4585 4589
4586 4590 #: rhodecode/model/validation_schema/schemas/integration_schema.py:79
4587 4591 msgid "Only repo admins can create integrations"
4588 4592 msgstr ""
4589 4593
4590 4594 #: rhodecode/model/validation_schema/schemas/integration_schema.py:86
4591 4595 msgid "Only repogroup admins can create integrations"
4592 4596 msgstr ""
4593 4597
4594 4598 #: rhodecode/model/validation_schema/schemas/integration_schema.py:91
4595 4599 msgid "Only superadmins can create global integrations"
4596 4600 msgstr ""
4597 4601
4598 4602 #: rhodecode/model/validation_schema/schemas/integration_schema.py:181
4599 4603 msgid "Scope of the integration. Recursive means the integration runs on all repos of that group and children recursively."
4600 4604 msgstr ""
4601 4605
4602 4606 #: rhodecode/model/validation_schema/schemas/integration_schema.py:184
4603 4607 msgid "Integration scope"
4604 4608 msgstr ""
4605 4609
4606 4610 #: rhodecode/model/validation_schema/schemas/integration_schema.py:214
4607 4611 msgid "General integration options"
4608 4612 msgstr ""
4609 4613
4610 4614 #: rhodecode/model/validation_schema/schemas/integration_schema.py:217
4611 4615 msgid "{integration_type} settings"
4612 4616 msgstr ""
4613 4617
4614 4618 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:52
4615 4619 msgid "Parent repository group `{}` does not exist"
4616 4620 msgstr ""
4617 4621
4618 4622 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:56
4619 4623 msgid "You do not have the permissions to store repository groups inside repository group `{}`"
4620 4624 msgstr ""
4621 4625
4622 4626 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:59
4623 4627 msgid "You do not have the permission to store repository groups in the root location."
4624 4628 msgstr ""
4625 4629
4626 4630 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:125
4627 4631 msgid "Repo group owner with id `{}` does not exists"
4628 4632 msgstr ""
4629 4633
4630 4634 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:143
4631 4635 #: rhodecode/model/validation_schema/schemas/repo_schema.py:221
4632 4636 msgid "Repository with name `{}` already exists"
4633 4637 msgstr ""
4634 4638
4635 4639 #: rhodecode/model/validation_schema/schemas/repo_group_schema.py:148
4636 4640 #: rhodecode/model/validation_schema/schemas/repo_schema.py:226
4637 4641 msgid "Repository group with name `{}` already exists"
4638 4642 msgstr ""
4639 4643
4640 4644 #: rhodecode/model/validation_schema/schemas/repo_schema.py:60
4641 4645 msgid "Repo owner with id `{}` does not exists"
4642 4646 msgstr ""
4643 4647
4644 4648 #: rhodecode/model/validation_schema/schemas/repo_schema.py:102
4645 4649 msgid "Fork with id `{}` does not exists"
4646 4650 msgstr ""
4647 4651
4648 4652 #: rhodecode/model/validation_schema/schemas/repo_schema.py:105
4649 4653 msgid "Cannot set fork of parameter of this repository to itself"
4650 4654 msgstr ""
4651 4655
4652 4656 #: rhodecode/model/validation_schema/schemas/repo_schema.py:130
4653 4657 #: rhodecode/model/validation_schema/schemas/repo_schema.py:134
4654 4658 msgid "Repository group `{}` does not exist"
4655 4659 msgstr ""
4656 4660
4657 4661 #: rhodecode/model/validation_schema/schemas/user_group_schema.py:32
4658 4662 msgid "Allowed in name are letters, numbers, and `-`, `_`, `.` Name must start with a letter or number. Got `{}`"
4659 4663 msgstr ""
4660 4664
4661 4665 #: rhodecode/model/validation_schema/schemas/user_group_schema.py:48
4662 4666 msgid "User group owner with id `{}` does not exists"
4663 4667 msgstr ""
4664 4668
4665 4669 #: rhodecode/model/validation_schema/schemas/user_schema.py:39
4666 4670 msgid "Password is incorrect"
4667 4671 msgstr ""
4668 4672
4669 4673 #: rhodecode/model/validation_schema/schemas/user_schema.py:62
4670 4674 msgid "New password must be different to old password"
4671 4675 msgstr ""
4672 4676
4673 4677 #: rhodecode/model/validation_schema/schemas/user_schema.py:183
4674 4678 msgid "Additional emails can be specified at <a href=\"{}\">extra emails</a> page."
4675 4679 msgstr ""
4676 4680
4677 4681 #: rhodecode/public/js/scripts.js:19
4678 4682 msgid ": , "
4679 4683 msgstr ""
4680 4684
4681 4685 #: rhodecode/public/js/scripts.js:20822 rhodecode/public/js/scripts.min.js:1
4682 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:64
4686 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:66
4683 4687 #: rhodecode/public/js/src/plugins/jquery.autocomplete.js:87
4684 4688 msgid "No results"
4685 4689 msgstr ""
4686 4690
4687 4691 #: rhodecode/public/js/scripts.js:22547 rhodecode/public/js/scripts.min.js:1
4688 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:170
4692 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:185
4689 4693 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:109
4690 4694 msgid "{0} year"
4691 4695 msgstr ""
4692 4696
4693 4697 #: rhodecode/public/js/scripts.js:22548 rhodecode/public/js/scripts.min.js:1
4694 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:158
4698 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:171
4695 4699 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:110
4696 4700 msgid "{0} month"
4697 4701 msgstr ""
4698 4702
4699 4703 #: rhodecode/public/js/scripts.js:22549 rhodecode/public/js/scripts.min.js:1
4700 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:153
4704 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:166
4701 4705 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:111
4702 4706 msgid "{0} day"
4703 4707 msgstr ""
4704 4708
4705 4709 #: rhodecode/public/js/scripts.js:22550 rhodecode/public/js/scripts.min.js:1
4706 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:155
4710 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:168
4707 4711 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:112
4708 4712 msgid "{0} hour"
4709 4713 msgstr ""
4710 4714
4711 4715 #: rhodecode/public/js/scripts.js:22551 rhodecode/public/js/scripts.min.js:1
4712 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:157
4716 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:170
4713 4717 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:113
4714 4718 msgid "{0} min"
4715 4719 msgstr ""
4716 4720
4717 4721 #: rhodecode/public/js/scripts.js:22552 rhodecode/public/js/scripts.min.js:1
4718 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:167
4722 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:180
4719 4723 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:114
4720 4724 msgid "{0} sec"
4721 4725 msgstr ""
4722 4726
4723 4727 #: rhodecode/public/js/scripts.js:22572 rhodecode/public/js/scripts.min.js:1
4724 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:133
4728 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:142
4725 4729 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:134
4726 4730 msgid "in {0}"
4727 4731 msgstr ""
4728 4732
4729 4733 #: rhodecode/public/js/scripts.js:22580 rhodecode/public/js/scripts.min.js:1
4730 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:150
4734 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:159
4731 4735 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:142
4732 4736 msgid "{0} ago"
4733 4737 msgstr ""
4734 4738
4735 4739 #: rhodecode/public/js/scripts.js:22592 rhodecode/public/js/scripts.min.js:1
4736 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:172
4740 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:187
4737 4741 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:154
4738 4742 msgid "{0}, {1} ago"
4739 4743 msgstr ""
4740 4744
4741 4745 #: rhodecode/public/js/scripts.js:22594 rhodecode/public/js/scripts.min.js:1
4742 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:135
4746 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:144
4743 4747 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:156
4744 4748 msgid "in {0}, {1}"
4745 4749 msgstr ""
4746 4750
4747 4751 #: rhodecode/public/js/scripts.js:22598 rhodecode/public/js/scripts.min.js:1
4748 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:151
4752 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:160
4749 4753 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:160
4750 4754 msgid "{0} and {1}"
4751 4755 msgstr ""
4752 4756
4753 4757 #: rhodecode/public/js/scripts.js:22600 rhodecode/public/js/scripts.min.js:1
4754 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:152
4758 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:161
4755 4759 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:162
4756 4760 msgid "{0} and {1} ago"
4757 4761 msgstr ""
4758 4762
4759 4763 #: rhodecode/public/js/scripts.js:22602 rhodecode/public/js/scripts.min.js:1
4760 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:134
4764 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:143
4761 4765 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:164
4762 4766 msgid "in {0} and {1}"
4763 4767 msgstr ""
4764 4768
4765 4769 #: rhodecode/public/js/scripts.js:37600 rhodecode/public/js/scripts.min.js:1
4766 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:51
4770 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:52
4767 4771 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:4
4768 4772 msgid "Loading more results..."
4769 4773 msgstr ""
4770 4774
4771 4775 #: rhodecode/public/js/scripts.js:37603 rhodecode/public/js/scripts.min.js:1
4772 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:82
4776 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:85
4773 4777 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:7
4774 4778 msgid "Searching..."
4775 4779 msgstr ""
4776 4780
4777 4781 #: rhodecode/public/js/scripts.js:37606 rhodecode/public/js/scripts.min.js:1
4778 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:57
4782 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:59
4779 4783 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:10
4780 4784 msgid "No matches found"
4781 4785 msgstr ""
4782 4786
4783 4787 #: rhodecode/public/js/scripts.js:37609 rhodecode/public/js/scripts.min.js:1
4784 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:50
4788 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:51
4785 4789 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:13
4786 4790 msgid "Loading failed"
4787 4791 msgstr ""
4788 4792
4789 4793 #: rhodecode/public/js/scripts.js:37613 rhodecode/public/js/scripts.min.js:1
4790 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:72
4794 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:74
4791 4795 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:17
4792 4796 msgid "One result is available, press enter to select it."
4793 4797 msgstr ""
4794 4798
4795 4799 #: rhodecode/public/js/scripts.js:37615 rhodecode/public/js/scripts.min.js:1
4796 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:166
4800 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:179
4797 4801 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:19
4798 4802 msgid "{0} results are available, use up and down arrow keys to navigate."
4799 4803 msgstr ""
4800 4804
4801 4805 #: rhodecode/public/js/scripts.js:37620 rhodecode/public/js/scripts.min.js:1
4802 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:77
4806 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:79
4803 4807 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:24
4804 4808 msgid "Please enter {0} or more character"
4805 4809 msgstr ""
4806 4810
4807 4811 #: rhodecode/public/js/scripts.js:37622 rhodecode/public/js/scripts.min.js:1
4808 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:78
4812 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:80
4809 4813 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:26
4810 4814 msgid "Please enter {0} or more characters"
4811 4815 msgstr ""
4812 4816
4813 4817 #: rhodecode/public/js/scripts.js:37627 rhodecode/public/js/scripts.min.js:1
4814 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:75
4818 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:77
4815 4819 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:31
4816 4820 msgid "Please delete {0} character"
4817 4821 msgstr ""
4818 4822
4819 4823 #: rhodecode/public/js/scripts.js:37629 rhodecode/public/js/scripts.min.js:1
4820 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:76
4824 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:78
4821 4825 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:33
4822 4826 msgid "Please delete {0} characters"
4823 4827 msgstr ""
4824 4828
4825 4829 #: rhodecode/public/js/scripts.js:37633 rhodecode/public/js/scripts.min.js:1
4826 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:123
4830 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:132
4827 4831 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:37
4828 4832 msgid "You can only select {0} item"
4829 4833 msgstr ""
4830 4834
4831 4835 #: rhodecode/public/js/scripts.js:37635 rhodecode/public/js/scripts.min.js:1
4832 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:124
4836 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:133
4833 4837 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:39
4834 4838 msgid "You can only select {0} items"
4835 4839 msgstr ""
4836 4840
4837 4841 #: rhodecode/public/js/scripts.js:38289 rhodecode/public/js/scripts.min.js:1
4838 4842 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:9
4839 4843 #: rhodecode/public/js/src/rhodecode/utils/ajax.js:139
4840 4844 msgid "Ajax Request Error"
4841 4845 msgstr ""
4842 4846
4843 4847 #: rhodecode/public/js/scripts.js:38691 rhodecode/public/js/scripts.min.js:1
4844 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:142
4848 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:151
4845 4849 #: rhodecode/public/js/src/rhodecode/changelog.js:35
4846 4850 msgid "showing {0} out of {1} commit"
4847 4851 msgstr ""
4848 4852
4849 4853 #: rhodecode/public/js/scripts.js:38693 rhodecode/public/js/scripts.min.js:1
4850 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:143
4854 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:152
4851 4855 #: rhodecode/public/js/src/rhodecode/changelog.js:37
4852 4856 msgid "showing {0} out of {1} commits"
4853 4857 msgstr ""
4854 4858
4855 #: rhodecode/public/js/scripts.js:39232 rhodecode/public/js/scripts.min.js:1
4856 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:85
4859 #: rhodecode/public/js/scripts.js:39237 rhodecode/public/js/scripts.min.js:1
4860 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:88
4857 4861 #: rhodecode/public/js/src/rhodecode/codemirror.js:368
4858 4862 msgid "Set status to Approved"
4859 4863 msgstr ""
4860 4864
4861 #: rhodecode/public/js/scripts.js:39252 rhodecode/public/js/scripts.min.js:1
4862 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:86
4865 #: rhodecode/public/js/scripts.js:39257 rhodecode/public/js/scripts.min.js:1
4866 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:89
4863 4867 #: rhodecode/public/js/src/rhodecode/codemirror.js:388
4864 4868 msgid "Set status to Rejected"
4865 4869 msgstr ""
4866 4870
4867 #: rhodecode/public/js/scripts.js:39271 rhodecode/public/js/scripts.min.js:1
4868 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:106
4871 #: rhodecode/public/js/scripts.js:39276 rhodecode/public/js/scripts.min.js:1
4872 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:112
4869 4873 #: rhodecode/public/js/src/rhodecode/codemirror.js:407
4870 4874 msgid "TODO comment"
4871 4875 msgstr ""
4872 4876
4873 #: rhodecode/public/js/scripts.js:39291 rhodecode/public/js/scripts.min.js:1
4874 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:71
4877 #: rhodecode/public/js/scripts.js:39296 rhodecode/public/js/scripts.min.js:1
4878 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:73
4875 4879 #: rhodecode/public/js/src/rhodecode/codemirror.js:427
4876 4880 msgid "Note Comment"
4877 4881 msgstr ""
4878 4882
4879 #: rhodecode/public/js/scripts.js:39592 rhodecode/public/js/scripts.js:39967
4883 #: rhodecode/public/js/scripts.js:39599 rhodecode/public/js/scripts.js:39987
4880 4884 #: rhodecode/public/js/scripts.min.js:1
4881 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:99
4885 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:103
4882 4886 #: rhodecode/public/js/src/rhodecode/codemirror.js:730
4883 4887 #: rhodecode/public/js/src/rhodecode/comments.js:267
4884 4888 msgid "Status Review"
4885 4889 msgstr ""
4886 4890
4887 #: rhodecode/public/js/scripts.js:39607 rhodecode/public/js/scripts.js:39982
4891 #: rhodecode/public/js/scripts.js:39614 rhodecode/public/js/scripts.js:40004
4888 4892 #: rhodecode/public/js/scripts.min.js:1
4889 4893 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:24
4890 4894 #: rhodecode/public/js/src/rhodecode/codemirror.js:745
4891 4895 #: rhodecode/public/js/src/rhodecode/comments.js:284
4892 4896 msgid "Comment text will be set automatically based on currently selected status ({0}) ..."
4893 4897 msgstr ""
4894 4898
4895 #: rhodecode/public/js/scripts.js:39688 rhodecode/public/js/scripts.js:40177
4896 #: rhodecode/public/js/scripts.js:41535 rhodecode/public/js/scripts.min.js:1
4897 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:48
4899 #: rhodecode/public/js/scripts.js:39695 rhodecode/public/js/scripts.js:40213
4900 #: rhodecode/public/js/scripts.js:41745 rhodecode/public/js/scripts.min.js:1
4901 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:49
4898 4902 #: rhodecode/public/js/src/rhodecode/codemirror.js:826
4899 4903 #: rhodecode/public/js/src/rhodecode/comments.js:493
4900 4904 #: rhodecode/public/js/src/rhodecode/files.js:499
4901 4905 #: rhodecode/templates/files/files_browser_tree.mako:57
4902 4906 msgid "Loading ..."
4903 4907 msgstr ""
4904 4908
4905 #: rhodecode/public/js/scripts.js:39849 rhodecode/public/js/scripts.min.js:1
4906 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:117
4907 msgid "Updated Comment"
4908 msgstr ""
4909
4910 #: rhodecode/public/js/scripts.js:39873 rhodecode/public/js/scripts.min.js:1
4911 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:141
4909 #: rhodecode/public/js/scripts.js:39860 rhodecode/public/js/scripts.min.js:1
4910 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:123
4911 #: rhodecode/public/js/src/rhodecode/comments.js:140
4912 msgid "Update Comment"
4913 msgstr ""
4914
4915 #: rhodecode/public/js/scripts.js:39884 rhodecode/public/js/scripts.min.js:1
4916 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:150
4912 4917 #: rhodecode/public/js/src/rhodecode/comments.js:164
4913 4918 msgid "resolve comment"
4914 4919 msgstr ""
4915 4920
4916 #: rhodecode/public/js/scripts.js:40126 rhodecode/public/js/scripts.min.js:1
4917 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:102
4921 #: rhodecode/public/js/scripts.js:40157 rhodecode/public/js/scripts.min.js:1
4922 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:83
4923 #: rhodecode/public/js/src/rhodecode/comments.js:437
4924 msgid "Saving Draft..."
4925 msgstr ""
4926
4927 #: rhodecode/public/js/scripts.js:40159 rhodecode/public/js/scripts.min.js:1
4928 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:108
4918 4929 #: rhodecode/public/js/src/rhodecode/comments.js:439
4919 4930 msgid "Submitting..."
4920 4931 msgstr ""
4921 4932
4922 #: rhodecode/public/js/scripts.js:40423 rhodecode/public/js/scripts.min.js:1
4923 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:122
4933 #: rhodecode/public/js/scripts.js:40481 rhodecode/public/js/scripts.min.js:1
4934 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:131
4924 4935 #: rhodecode/public/js/src/rhodecode/comments.js:761
4925 4936 msgid "Yes, delete comment #{0}!"
4926 4937 msgstr ""
4927 4938
4928 #: rhodecode/public/js/scripts.js:40487 rhodecode/public/js/scripts.min.js:1
4929 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:46
4939 #: rhodecode/public/js/scripts.js:40526 rhodecode/public/js/scripts.min.js:1
4940 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:106
4941 #: rhodecode/public/js/src/rhodecode/comments.js:806
4942 msgid "Submit {0} draft comment."
4943 msgstr ""
4944
4945 #: rhodecode/public/js/scripts.js:40529 rhodecode/public/js/scripts.min.js:1
4946 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:130
4947 #: rhodecode/public/js/src/rhodecode/comments.js:809
4948 msgid "Yes"
4949 msgstr ""
4950
4951 #: rhodecode/public/js/scripts.js:40621 rhodecode/public/js/scripts.min.js:1
4952 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:47
4930 4953 #: rhodecode/public/js/src/rhodecode/comments.js:901
4931 4954 msgid "Leave a resolution comment, or click resolve button to resolve TODO comment #{0}"
4932 4955 msgstr ""
4933 4956
4934 #: rhodecode/public/js/scripts.js:40696 rhodecode/public/js/scripts.min.js:1
4957 #: rhodecode/public/js/scripts.js:40825 rhodecode/public/js/scripts.min.js:1
4935 4958 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:23
4936 4959 #: rhodecode/public/js/src/rhodecode/comments.js:1105
4937 4960 msgid "Comment body was not changed."
4938 4961 msgstr ""
4939 4962
4940 #: rhodecode/public/js/scripts.js:40875 rhodecode/public/js/scripts.min.js:1
4963 #: rhodecode/public/js/scripts.js:41071 rhodecode/public/js/scripts.min.js:1
4941 4964 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:44
4942 msgid "Leave a comment on line {0}."
4943 msgstr ""
4944
4945 #: rhodecode/public/js/scripts.js:41006 rhodecode/public/js/scripts.min.js:1
4946 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:107
4947 #: rhodecode/public/js/src/rhodecode/comments.js:1491
4965 #: rhodecode/public/js/src/rhodecode/comments.js:1351
4966 msgid "Leave a comment on file {0} line {1}."
4967 msgstr ""
4968
4969 #: rhodecode/public/js/scripts.js:41212 rhodecode/public/js/scripts.min.js:1
4970 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:113
4971 #: rhodecode/public/js/src/rhodecode/comments.js:1492
4948 4972 msgid "TODO from comment {0} was fixed."
4949 4973 msgstr ""
4950 4974
4951 #: rhodecode/public/js/scripts.js:41284 rhodecode/public/js/scripts.min.js:1
4952 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:145
4975 #: rhodecode/public/js/scripts.js:41494 rhodecode/public/js/scripts.min.js:1
4976 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:154
4953 4977 #: rhodecode/public/js/src/rhodecode/files.js:248
4954 4978 msgid "truncated result"
4955 4979 msgstr ""
4956 4980
4957 #: rhodecode/public/js/scripts.js:41286 rhodecode/public/js/scripts.min.js:1
4958 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:146
4981 #: rhodecode/public/js/scripts.js:41496 rhodecode/public/js/scripts.min.js:1
4982 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:155
4959 4983 #: rhodecode/public/js/src/rhodecode/files.js:250
4960 4984 msgid "truncated results"
4961 4985 msgstr ""
4962 4986
4963 #: rhodecode/public/js/scripts.js:41295 rhodecode/public/js/scripts.min.js:1
4964 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:58
4987 #: rhodecode/public/js/scripts.js:41505 rhodecode/public/js/scripts.min.js:1
4988 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:60
4965 4989 #: rhodecode/public/js/src/rhodecode/files.js:259
4966 4990 msgid "No matching files"
4967 4991 msgstr ""
4968 4992
4969 #: rhodecode/public/js/scripts.js:41353 rhodecode/public/js/scripts.min.js:1
4970 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:83
4993 #: rhodecode/public/js/scripts.js:41563 rhodecode/public/js/scripts.min.js:1
4994 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:86
4971 4995 #: rhodecode/public/js/src/rhodecode/files.js:317
4972 4996 msgid "Selection link"
4973 4997 msgstr ""
4974 4998
4975 #: rhodecode/public/js/scripts.js:41450 rhodecode/public/js/scripts.min.js:1
4999 #: rhodecode/public/js/scripts.js:41660 rhodecode/public/js/scripts.min.js:1
4976 5000 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:10
4977 5001 #: rhodecode/public/js/src/rhodecode/files.js:414
4978 5002 msgid "All Authors"
4979 5003 msgstr ""
4980 5004
4981 #: rhodecode/public/js/scripts.js:41600 rhodecode/public/js/scripts.js:41603
5005 #: rhodecode/public/js/scripts.js:41810 rhodecode/public/js/scripts.js:41813
4982 5006 #: rhodecode/public/js/scripts.min.js:1
4983 5007 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:38
4984 5008 #: rhodecode/public/js/src/rhodecode/files.js:564
4985 5009 #: rhodecode/public/js/src/rhodecode/files.js:567
4986 5010 msgid "File `{0}` has a newer version available, or has been removed. Click {1} to see the latest version."
4987 5011 msgstr ""
4988 5012
4989 #: rhodecode/public/js/scripts.js:41606 rhodecode/public/js/scripts.min.js:1
4990 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:111
5013 #: rhodecode/public/js/scripts.js:41816 rhodecode/public/js/scripts.min.js:1
5014 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:117
4991 5015 #: rhodecode/public/js/src/rhodecode/files.js:570
4992 5016 msgid "There is an existing path `{0}` at this commit."
4993 5017 msgstr ""
4994 5018
4995 #: rhodecode/public/js/scripts.js:41609 rhodecode/public/js/scripts.min.js:1
4996 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:110
5019 #: rhodecode/public/js/scripts.js:41819 rhodecode/public/js/scripts.min.js:1
5020 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:116
4997 5021 #: rhodecode/public/js/src/rhodecode/files.js:573
4998 5022 msgid "There is a later version of file tree available. Click {0} to create a file at the latest tree."
4999 5023 msgstr ""
5000 5024
5001 #: rhodecode/public/js/scripts.js:41663 rhodecode/public/js/scripts.min.js:1
5002 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:101
5025 #: rhodecode/public/js/scripts.js:41873 rhodecode/public/js/scripts.min.js:1
5026 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:105
5003 5027 #: rhodecode/public/js/src/rhodecode/followers.js:26
5004 5028 msgid "Stopped watching this repository"
5005 5029 msgstr ""
5006 5030
5007 #: rhodecode/public/js/scripts.js:41664 rhodecode/public/js/scripts.min.js:1
5008 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:121
5031 #: rhodecode/public/js/scripts.js:41874 rhodecode/public/js/scripts.min.js:1
5032 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:129
5009 5033 #: rhodecode/public/js/src/rhodecode/followers.js:27
5010 5034 #: rhodecode/templates/base/base.mako:310
5011 5035 msgid "Watch"
5012 5036 msgstr ""
5013 5037
5014 #: rhodecode/public/js/scripts.js:41667 rhodecode/public/js/scripts.min.js:1
5015 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:98
5038 #: rhodecode/public/js/scripts.js:41877 rhodecode/public/js/scripts.min.js:1
5039 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:102
5016 5040 #: rhodecode/public/js/src/rhodecode/followers.js:30
5017 5041 msgid "Started watching this repository"
5018 5042 msgstr ""
5019 5043
5020 #: rhodecode/public/js/scripts.js:41668 rhodecode/public/js/scripts.min.js:1
5021 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:116
5044 #: rhodecode/public/js/scripts.js:41878 rhodecode/public/js/scripts.min.js:1
5045 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:122
5022 5046 #: rhodecode/public/js/src/rhodecode/followers.js:31
5023 5047 #: rhodecode/templates/base/base.mako:308
5024 5048 msgid "Unwatch"
5025 5049 msgstr ""
5026 5050
5027 #: rhodecode/public/js/scripts.js:42165 rhodecode/public/js/scripts.min.js:1
5051 #: rhodecode/public/js/scripts.js:42384 rhodecode/public/js/scripts.min.js:1
5028 5052 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:12
5029 5053 #: rhodecode/public/js/src/rhodecode/pullrequests.js:185
5030 5054 msgid "All reviewers must vote."
5031 5055 msgstr ""
5032 5056
5033 #: rhodecode/public/js/scripts.js:42174 rhodecode/public/js/scripts.min.js:1
5034 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:11
5035 msgid "All individual reviewers must vote."
5036 msgstr ""
5037
5038 #: rhodecode/public/js/scripts.js:42179 rhodecode/public/js/scripts.min.js:1
5039 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:14
5040 msgid "At least {0} reviewer must vote."
5041 msgstr ""
5042
5043 #: rhodecode/public/js/scripts.js:42185 rhodecode/public/js/scripts.min.js:1
5044 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:15
5045 msgid "At least {0} reviewers must vote."
5046 msgstr ""
5047
5048 #: rhodecode/public/js/scripts.js:42201 rhodecode/public/js/scripts.min.js:1
5049 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:80
5050 msgid "Reviewers picked from source code changes."
5051 msgstr ""
5052
5053 #: rhodecode/public/js/scripts.js:42209 rhodecode/public/js/scripts.min.js:1
5054 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:8
5055 msgid "Adding new reviewers is forbidden."
5056 msgstr ""
5057
5058 #: rhodecode/public/js/scripts.js:42217 rhodecode/public/js/scripts.min.js:1
5059 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:17
5060 msgid "Author is not allowed to be a reviewer."
5061 msgstr ""
5062
5063 #: rhodecode/public/js/scripts.js:42231 rhodecode/public/js/scripts.min.js:1
5064 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:25
5065 msgid "Commit Authors are not allowed to be a reviewer."
5066 msgstr ""
5067
5068 #: rhodecode/public/js/scripts.js:42238 rhodecode/public/js/scripts.min.js:1
5069 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:65
5070 msgid "No review rules set."
5071 msgstr ""
5072
5073 #: rhodecode/public/js/scripts.js:42283 rhodecode/public/js/scripts.min.js:1
5074 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:49
5057 #: rhodecode/public/js/scripts.js:42408 rhodecode/public/js/scripts.min.js:1
5058 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:54
5059 #: rhodecode/public/js/src/rhodecode/pullrequests.js:209
5060 msgid "No additional review rules set."
5061 msgstr ""
5062
5063 #: rhodecode/public/js/scripts.js:42454 rhodecode/public/js/scripts.min.js:1
5064 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:50
5075 5065 #: rhodecode/public/js/src/rhodecode/pullrequests.js:255
5076 5066 msgid "Loading diff ..."
5077 5067 msgstr ""
5078 5068
5079 #: rhodecode/public/js/scripts.js:42336 rhodecode/public/js/scripts.min.js:1
5080 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:109
5069 #: rhodecode/public/js/scripts.js:42507 rhodecode/public/js/scripts.min.js:1
5070 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:115
5081 5071 #: rhodecode/public/js/src/rhodecode/pullrequests.js:308
5082 5072 msgid "There are no commits to merge."
5083 5073 msgstr ""
5084 5074
5085 #: rhodecode/public/js/scripts.js:42408 rhodecode/public/js/scripts.min.js:1
5086 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:120
5075 #: rhodecode/public/js/scripts.js:42579 rhodecode/public/js/scripts.min.js:1
5076 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:128
5087 5077 #: rhodecode/public/js/src/rhodecode/pullrequests.js:380
5088 5078 msgid "User `{0}` not allowed to be a reviewer"
5089 5079 msgstr ""
5090 5080
5091 #: rhodecode/public/js/scripts.js:42414 rhodecode/public/js/scripts.min.js:1
5081 #: rhodecode/public/js/scripts.js:42585 rhodecode/public/js/scripts.min.js:1
5082 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:127
5092 5083 #: rhodecode/public/js/src/rhodecode/pullrequests.js:386
5093 5084 msgid "User `{0}` already in reviewers/observers"
5094 5085 msgstr ""
5095 5086
5096 #: rhodecode/public/js/scripts.js:42528 rhodecode/public/js/scripts.min.js:1
5097 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:126
5098 #: rhodecode/public/js/src/rhodecode/pullrequests.js:500
5087 #: rhodecode/public/js/scripts.js:42699 rhodecode/public/js/scripts.min.js:1
5088 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:135
5089 #: rhodecode/public/js/src/rhodecode/pullrequests.js:501
5099 5090 msgid "added manually by \"{0}\""
5100 5091 msgstr ""
5101 5092
5102 #: rhodecode/public/js/scripts.js:42533 rhodecode/public/js/scripts.min.js:1
5103 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:138
5104 #: rhodecode/public/js/src/rhodecode/pullrequests.js:505
5093 #: rhodecode/public/js/scripts.js:42704 rhodecode/public/js/scripts.min.js:1
5094 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:147
5095 #: rhodecode/public/js/src/rhodecode/pullrequests.js:506
5105 5096 msgid "member of \"{0}\""
5106 5097 msgstr ""
5107 5098
5108 #: rhodecode/public/js/scripts.js:42766 rhodecode/public/js/scripts.min.js:1
5109 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:118
5110 #: rhodecode/public/js/src/rhodecode/pullrequests.js:738
5099 #: rhodecode/public/js/scripts.js:42937 rhodecode/public/js/scripts.min.js:1
5100 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:125
5101 #: rhodecode/public/js/src/rhodecode/pullrequests.js:739
5111 5102 msgid "Updating..."
5112 5103 msgstr ""
5113 5104
5114 #: rhodecode/public/js/scripts.js:42776 rhodecode/public/js/scripts.min.js:1
5105 #: rhodecode/public/js/scripts.js:42947 rhodecode/public/js/scripts.min.js:1
5115 5106 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:40
5116 #: rhodecode/public/js/src/rhodecode/pullrequests.js:748
5107 #: rhodecode/public/js/src/rhodecode/pullrequests.js:749
5117 5108 msgid "Force updating..."
5118 5109 msgstr ""
5119 5110
5120 #: rhodecode/public/js/scripts.js:47613 rhodecode/public/js/scripts.min.js:1
5121 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:94
5111 #: rhodecode/public/js/scripts.js:47812 rhodecode/public/js/scripts.min.js:1
5112 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:98
5122 5113 #: rhodecode/public/js/src/rhodecode/users.js:54
5123 5114 msgid "Show this authentication token?"
5124 5115 msgstr ""
5125 5116
5126 #: rhodecode/public/js/scripts.js:47615 rhodecode/public/js/scripts.min.js:1
5127 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:87
5117 #: rhodecode/public/js/scripts.js:47814 rhodecode/public/js/scripts.min.js:1
5118 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:90
5128 5119 #: rhodecode/public/js/src/rhodecode/users.js:56
5129 5120 msgid "Show"
5130 5121 msgstr ""
5131 5122
5132 #: rhodecode/public/js/scripts.js:47651 rhodecode/public/js/scripts.min.js:1
5123 #: rhodecode/public/js/scripts.js:47850 rhodecode/public/js/scripts.min.js:1
5133 5124 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:16
5134 5125 #: rhodecode/public/js/src/rhodecode/users.js:92
5135 5126 msgid "Authentication Token"
5136 5127 msgstr ""
5137 5128
5138 #: rhodecode/public/js/scripts.js:47840 rhodecode/public/js/scripts.min.js:1
5139 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:130
5129 #: rhodecode/public/js/scripts.js:48039 rhodecode/public/js/scripts.min.js:1
5130 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:139
5140 5131 #: rhodecode/public/js/src/rhodecode.js:144
5141 5132 msgid "file"
5142 5133 msgstr ""
5143 5134
5144 #: rhodecode/public/js/scripts.js:47984 rhodecode/public/js/scripts.min.js:1
5145 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:52
5135 #: rhodecode/public/js/scripts.js:48183 rhodecode/public/js/scripts.min.js:1
5136 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:53
5146 5137 #: rhodecode/public/js/src/rhodecode.js:288
5147 5138 msgid "Loading..."
5148 5139 msgstr ""
5149 5140
5150 #: rhodecode/public/js/scripts.js:48366 rhodecode/public/js/scripts.min.js:1
5151 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:127
5141 #: rhodecode/public/js/scripts.js:48565 rhodecode/public/js/scripts.min.js:1
5142 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:136
5152 5143 #: rhodecode/public/js/src/rhodecode.js:670
5153 5144 msgid "date not in future"
5154 5145 msgstr ""
5155 5146
5156 #: rhodecode/public/js/scripts.js:48374 rhodecode/public/js/scripts.min.js:1
5157 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:96
5147 #: rhodecode/public/js/scripts.js:48573 rhodecode/public/js/scripts.min.js:1
5148 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:100
5158 5149 #: rhodecode/public/js/src/rhodecode.js:678
5159 5150 msgid "Specified expiration date"
5160 5151 msgstr ""
5161 5152
5162 5153 #: rhodecode/public/js/scripts.min.js:1
5163 5154 msgid "action"
5164 5155 msgstr ""
5165 5156
5166 5157 #: rhodecode/public/js/scripts.min.js:1
5167 5158 msgid "target"
5168 5159 msgstr ""
5169 5160
5170 5161 #: rhodecode/public/js/scripts.min.js:1
5171 5162 msgid "text"
5172 5163 msgstr ""
5173 5164
5174 5165 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:2
5175 5166 msgid "(from usergroup {0})"
5176 5167 msgstr ""
5177 5168
5178 5169 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:3
5179 5170 msgid "<strong>, and {0} file</strong> changed."
5180 5171 msgstr ""
5181 5172
5182 5173 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:4
5183 5174 msgid "<strong>, and {0} files</strong> changed."
5184 5175 msgstr ""
5185 5176
5186 5177 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:5
5187 5178 msgid "<strong>{0} file</strong> changed, "
5188 5179 msgstr ""
5189 5180
5190 5181 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:6
5191 5182 msgid "<strong>{0} files</strong> changed, "
5192 5183 msgstr ""
5193 5184
5194 5185 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:7
5195 5186 msgid "Add another comment"
5196 5187 msgstr ""
5197 5188
5189 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:8
5190 msgid "Adding new reviewers is forbidden."
5191 msgstr ""
5192
5193 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:11
5194 msgid "All individual reviewers must vote."
5195 msgstr ""
5196
5198 5197 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:13
5199 5198 msgid "Are you sure to close this pull request without merging?"
5200 5199 msgstr ""
5201 5200
5201 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:14
5202 msgid "At least {0} reviewer must vote."
5203 msgstr ""
5204
5205 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:15
5206 msgid "At least {0} reviewers must vote."
5207 msgstr ""
5208
5209 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:17
5210 msgid "Author is not allowed to be a reviewer."
5211 msgstr ""
5212
5202 5213 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:18
5203 5214 msgid "Changed files"
5204 5215 msgstr ""
5205 5216
5206 5217 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:19
5207 5218 #: rhodecode/public/js/src/i18n_messages.js:5
5208 #: rhodecode/templates/pullrequests/pullrequest_show.mako:623
5209 #: rhodecode/templates/pullrequests/pullrequest_show.mako:626
5210 #: rhodecode/templates/pullrequests/pullrequest_show.mako:680
5219 #: rhodecode/templates/pullrequests/pullrequest_show.mako:610
5220 #: rhodecode/templates/pullrequests/pullrequest_show.mako:613
5221 #: rhodecode/templates/pullrequests/pullrequest_show.mako:676
5211 5222 msgid "Close"
5212 5223 msgstr ""
5213 5224
5214 5225 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:20
5215 5226 #: rhodecode/templates/codeblocks/diffs.mako:119
5216 5227 msgid "Collapse all files"
5217 5228 msgstr ""
5218 5229
5219 5230 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:21
5220 5231 msgid "Collapse {0} commit"
5221 5232 msgstr ""
5222 5233
5223 5234 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:22
5224 5235 msgid "Collapse {0} commits"
5225 5236 msgstr ""
5226 5237
5238 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:25
5239 msgid "Commit Authors are not allowed to be a reviewer."
5240 msgstr ""
5241
5227 5242 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:26
5228 5243 msgid "Compare summary: <strong>{0} commit</strong>"
5229 5244 msgstr ""
5230 5245
5231 5246 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:27
5232 5247 msgid "Compare summary: <strong>{0} commits</strong>"
5233 5248 msgstr ""
5234 5249
5235 5250 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:28
5236 5251 msgid "Context file: "
5237 5252 msgstr ""
5238 5253
5239 5254 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:30
5240 5255 msgid "Delete this comment?"
5241 5256 msgstr ""
5242 5257
5243 5258 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:31
5244 5259 msgid "Diff to Commit "
5245 5260 msgstr ""
5246 5261
5247 5262 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:32
5248 5263 msgid "Error during search operation"
5249 5264 msgstr ""
5250 5265
5251 5266 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:33
5252 5267 #: rhodecode/templates/codeblocks/diffs.mako:117
5253 5268 msgid "Expand all files"
5254 5269 msgstr ""
5255 5270
5256 5271 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:34
5257 5272 msgid "Expand {0} commit"
5258 5273 msgstr ""
5259 5274
5260 5275 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:35
5261 5276 msgid "Expand {0} commits"
5262 5277 msgstr ""
5263 5278
5264 5279 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:36
5265 5280 msgid "Fetching repository state failed. Error code: {0} {1}. Try <a href=\"{2}\">refreshing</a> this page."
5266 5281 msgstr ""
5267 5282
5268 5283 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:37
5269 5284 msgid "Fetching repository state failed. Error code: {0} {1}. Try refreshing this page."
5270 5285 msgstr ""
5271 5286
5272 5287 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:39
5273 5288 msgid "Follow"
5274 5289 msgstr ""
5275 5290
5276 5291 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:41
5292 #: rhodecode/templates/codeblocks/diffs.mako:988
5277 5293 msgid "Hide full context diff"
5278 5294 msgstr ""
5279 5295
5280 5296 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:42
5297 #: rhodecode/templates/codeblocks/diffs.mako:980
5281 5298 msgid "Hide whitespace changes"
5282 5299 msgstr ""
5283 5300
5284 5301 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:43
5285 5302 #: rhodecode/public/js/src/i18n_messages.js:4
5286 5303 msgid "Invite reviewers to this discussion"
5287 5304 msgstr ""
5288 5305
5289 5306 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:45
5307 msgid "Leave a comment on line {0}."
5308 msgstr ""
5309
5310 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:46
5290 5311 msgid "Leave a comment, or click resolve button to resolve TODO comment #{0}"
5291 5312 msgstr ""
5292 5313
5293 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:53
5294 msgid "No bookmarks available yet."
5295 msgstr ""
5296
5297 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:54
5298 msgid "No branches available yet."
5299 msgstr ""
5300
5301 5314 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:55
5302 msgid "No forks available yet."
5315 msgid "No bookmarks available yet."
5303 5316 msgstr ""
5304 5317
5305 5318 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:56
5319 msgid "No branches available yet."
5320 msgstr ""
5321
5322 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:57
5323 msgid "No forks available yet."
5324 msgstr ""
5325
5326 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:58
5306 5327 msgid "No gists available yet."
5307 5328 msgstr ""
5308 5329
5309 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:59
5310 msgid "No pull requests available yet."
5311 msgstr ""
5312
5313 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:60
5314 msgid "No repositories available yet."
5315 msgstr ""
5316
5317 5330 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:61
5318 msgid "No repositories present."
5331 msgid "No pull requests available yet."
5319 5332 msgstr ""
5320 5333
5321 5334 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:62
5322 msgid "No repository groups available yet."
5335 msgid "No repositories available yet."
5323 5336 msgstr ""
5324 5337
5325 5338 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:63
5339 msgid "No repositories present."
5340 msgstr ""
5341
5342 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:64
5343 msgid "No repository groups available yet."
5344 msgstr ""
5345
5346 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:65
5326 5347 msgid "No repository groups present."
5327 5348 msgstr ""
5328 5349
5329 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:66
5330 msgid "No ssh keys available yet."
5331 msgstr ""
5332
5333 5350 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:67
5334 msgid "No tags available yet."
5351 msgid "No review rules set."
5335 5352 msgstr ""
5336 5353
5337 5354 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:68
5338 msgid "No user groups available yet."
5355 msgid "No ssh keys available yet."
5339 5356 msgstr ""
5340 5357
5341 5358 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:69
5359 msgid "No tags available yet."
5360 msgstr ""
5361
5362 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:70
5363 msgid "No user groups available yet."
5364 msgstr ""
5365
5366 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:71
5342 5367 msgid "No users available yet."
5343 5368 msgstr ""
5344 5369
5345 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:73
5370 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:75
5346 5371 #: rhodecode/templates/commits/changelog.mako:78
5347 5372 msgid "Open new pull request"
5348 5373 msgstr ""
5349 5374
5350 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:74
5375 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:76
5351 5376 msgid "Open new pull request for selected commit"
5352 5377 msgstr ""
5353 5378
5354 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:79
5355 msgid "Please wait creating pull request..."
5356 msgstr ""
5357
5358 5379 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:81
5359 msgid "Saving..."
5380 msgid "Please wait creating pull request..."
5381 msgstr ""
5382
5383 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:82
5384 msgid "Reviewers picked from source code changes."
5360 5385 msgstr ""
5361 5386
5362 5387 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:84
5388 msgid "Saving..."
5389 msgstr ""
5390
5391 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:87
5363 5392 #: rhodecode/public/js/src/i18n_messages.js:6
5364 5393 #: rhodecode/templates/admin/settings/settings_email.mako:50
5365 5394 msgid "Send"
5366 5395 msgstr ""
5367 5396
5368 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:88
5397 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:91
5369 5398 msgid "Show at Commit "
5370 5399 msgstr ""
5371 5400
5372 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:89
5373 msgid "Show commit range {0} ... {1}"
5374 msgstr ""
5375
5376 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:90
5377 msgid "Show full context diff"
5378 msgstr ""
5379
5380 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:91
5381 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:40
5382 msgid "Show more"
5383 msgstr ""
5384
5385 5401 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:92
5386 msgid "Show selected commit __S"
5402 msgid "Show commit range {0} ... {1}"
5387 5403 msgstr ""
5388 5404
5389 5405 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:93
5390 msgid "Show selected commits __S ... __E"
5406 msgid "Show commit range {0}<i class=\"icon-angle-right\"></i>{1}"
5407 msgstr ""
5408
5409 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:94
5410 #: rhodecode/templates/codeblocks/diffs.mako:990
5411 msgid "Show full context diff"
5391 5412 msgstr ""
5392 5413
5393 5414 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:95
5394 msgid "Show whitespace changes"
5415 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:40
5416 msgid "Show more"
5417 msgstr ""
5418
5419 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:96
5420 msgid "Show selected commit __S"
5395 5421 msgstr ""
5396 5422
5397 5423 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:97
5424 msgid "Show selected commits __S ... __E"
5425 msgstr ""
5426
5427 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:99
5428 #: rhodecode/templates/codeblocks/diffs.mako:978
5429 msgid "Show whitespace changes"
5430 msgstr ""
5431
5432 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:101
5398 5433 msgid "Start following this repository"
5399 5434 msgstr ""
5400 5435
5401 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:100
5402 msgid "Stop following this repository"
5403 msgstr ""
5404
5405 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:103
5406 msgid "Switch target repository with the source."
5407 msgstr ""
5408
5409 5436 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:104
5437 msgid "Stop following this repository"
5438 msgstr ""
5439
5440 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:107
5441 msgid "Submit {0} draft comments."
5442 msgstr ""
5443
5444 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:109
5445 msgid "Switch target repository with the source."
5446 msgstr ""
5447
5448 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:110
5410 5449 #: rhodecode/public/js/src/i18n_messages.js:7
5411 5450 msgid "Switch to chat"
5412 5451 msgstr ""
5413 5452
5414 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:105
5453 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:111
5415 5454 #: rhodecode/public/js/src/i18n_messages.js:8
5416 5455 msgid "Switch to comment"
5417 5456 msgstr ""
5418 5457
5419 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:108
5420 msgid "There are currently no open pull requests requiring your participation."
5421 msgstr ""
5422
5423 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:112
5424 msgid "This pull requests will consist of <strong>{0} commit</strong>."
5425 msgstr ""
5426
5427 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:113
5428 msgid "This pull requests will consist of <strong>{0} commits</strong>."
5429 msgstr ""
5430
5431 5458 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:114
5432 msgid "Toggle Wide Mode diff"
5433 msgstr ""
5434
5435 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:115
5436 msgid "Unfollow"
5459 msgid "There are currently no open pull requests requiring your participation."
5460 msgstr ""
5461
5462 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:118
5463 msgid "This pull requests will consist of <strong>{0} commit</strong>."
5437 5464 msgstr ""
5438 5465
5439 5466 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:119
5467 msgid "This pull requests will consist of <strong>{0} commits</strong>."
5468 msgstr ""
5469
5470 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:120
5471 msgid "Toggle Wide Mode diff"
5472 msgstr ""
5473
5474 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:121
5475 msgid "Unfollow"
5476 msgstr ""
5477
5478 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:124
5479 msgid "Updated Comment"
5480 msgstr ""
5481
5482 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:126
5440 5483 msgid "User `{0}` already in reviewers"
5441 5484 msgstr ""
5442 5485
5443 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:125
5486 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:134
5444 5487 #: rhodecode/templates/admin/auth/auth_settings.mako:69
5445 5488 msgid "activated"
5446 5489 msgstr ""
5447 5490
5448 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:128
5449 msgid "disabled"
5450 msgstr ""
5451
5452 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:129
5453 msgid "enabled"
5454 msgstr ""
5455
5456 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:131
5457 msgid "files"
5458 msgstr ""
5459
5460 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:132
5461 msgid "go to numeric commit"
5462 msgstr ""
5463
5464 5491 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:137
5492 msgid "disabled"
5493 msgstr ""
5494
5495 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:138
5496 msgid "enabled"
5497 msgstr ""
5498
5499 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:140
5500 msgid "files"
5501 msgstr ""
5502
5503 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:141
5504 msgid "go to numeric commit"
5505 msgstr ""
5506
5507 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:146
5465 5508 #: rhodecode/templates/index_base.mako:27
5466 5509 #: rhodecode/templates/pullrequests/pullrequest.mako:154
5467 5510 #: rhodecode/templates/pullrequests/pullrequest.mako:178
5468 5511 msgid "loading..."
5469 5512 msgstr ""
5470 5513
5471 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:139
5514 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:148
5472 5515 msgid "no commits"
5473 5516 msgstr ""
5474 5517
5475 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:140
5476 #: rhodecode/templates/admin/auth/auth_settings.mako:69
5477 msgid "not active"
5478 msgstr ""
5479
5480 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:144
5481 msgid "specify commit"
5482 msgstr ""
5483
5484 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:147
5485 msgid "{0} ({1} inactive) of {2} user groups ({3} inactive)"
5486 msgstr ""
5487
5488 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:148
5489 msgid "{0} ({1} inactive) of {2} users ({3} inactive)"
5490 msgstr ""
5491
5492 5518 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:149
5493 msgid "{0} active out of {1} users"
5494 msgstr ""
5495
5496 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:154
5497 msgid "{0} days"
5519 #: rhodecode/templates/admin/auth/auth_settings.mako:69
5520 msgid "not active"
5521 msgstr ""
5522
5523 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:153
5524 msgid "specify commit"
5498 5525 msgstr ""
5499 5526
5500 5527 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:156
5501 msgid "{0} hours"
5502 msgstr ""
5503
5504 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:159
5505 msgid "{0} months"
5506 msgstr ""
5507
5508 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:160
5509 msgid "{0} of {1} repositories"
5510 msgstr ""
5511
5512 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:161
5513 msgid "{0} of {1} repository groups"
5528 msgid "{0} ({1} inactive) of {2} user groups ({3} inactive)"
5529 msgstr ""
5530
5531 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:157
5532 msgid "{0} ({1} inactive) of {2} users ({3} inactive)"
5533 msgstr ""
5534
5535 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:158
5536 msgid "{0} active out of {1} users"
5514 5537 msgstr ""
5515 5538
5516 5539 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:162
5517 msgid "{0} out of {1} ssh keys"
5540 msgid "{0} bookmark"
5518 5541 msgstr ""
5519 5542
5520 5543 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:163
5521 msgid "{0} out of {1} users"
5544 msgid "{0} bookmarks"
5522 5545 msgstr ""
5523 5546
5524 5547 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:164
5525 msgid "{0} repositories"
5548 msgid "{0} branch"
5526 5549 msgstr ""
5527 5550
5528 5551 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:165
5529 msgid "{0} repository groups"
5530 msgstr ""
5531
5532 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:168
5533 msgid "{0} user groups ({1} inactive)"
5552 msgid "{0} branches"
5553 msgstr ""
5554
5555 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:167
5556 msgid "{0} days"
5534 5557 msgstr ""
5535 5558
5536 5559 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:169
5560 msgid "{0} hours"
5561 msgstr ""
5562
5563 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:172
5564 msgid "{0} months"
5565 msgstr ""
5566
5567 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:173
5568 msgid "{0} of {1} repositories"
5569 msgstr ""
5570
5571 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:174
5572 msgid "{0} of {1} repository groups"
5573 msgstr ""
5574
5575 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:175
5576 msgid "{0} out of {1} ssh keys"
5577 msgstr ""
5578
5579 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:176
5580 msgid "{0} out of {1} users"
5581 msgstr ""
5582
5583 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:177
5584 msgid "{0} repositories"
5585 msgstr ""
5586
5587 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:178
5588 msgid "{0} repository groups"
5589 msgstr ""
5590
5591 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:181
5592 msgid "{0} tag"
5593 msgstr ""
5594
5595 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:182
5596 msgid "{0} tags"
5597 msgstr ""
5598
5599 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:183
5600 msgid "{0} user groups ({1} inactive)"
5601 msgstr ""
5602
5603 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:184
5537 5604 msgid "{0} users ({1} inactive)"
5538 5605 msgstr ""
5539 5606
5540 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:171
5607 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:186
5541 5608 msgid "{0} years"
5542 5609 msgstr ""
5543 5610
5544 5611 #: rhodecode/public/js/src/math_jax/extensions/HelpDialog.js:19
5545 5612 msgid "HelpDialog"
5546 5613 msgstr ""
5547 5614
5548 5615 #: rhodecode/public/js/src/math_jax/jax/output/HTML-CSS/autoload/mglyph.js:19
5549 5616 msgid "MathML"
5550 5617 msgstr ""
5551 5618
5552 #: rhodecode/public/js/src/rhodecode/comments.js:140
5553 msgid "Update Comment"
5554 msgstr ""
5555
5556 #: rhodecode/public/js/src/rhodecode/comments.js:437
5557 msgid "Saving Draft..."
5558 msgstr ""
5559
5560 #: rhodecode/public/js/src/rhodecode/comments.js:806
5561 msgid "Submit {0} draft comment."
5562 msgstr ""
5563
5564 #: rhodecode/public/js/src/rhodecode/comments.js:809
5565 msgid "Yes"
5566 msgstr ""
5567
5568 #: rhodecode/public/js/src/rhodecode/comments.js:1351
5569 msgid "Leave a comment on file {0} line {1}."
5570 msgstr ""
5571
5572 #: rhodecode/public/js/src/rhodecode/pullrequests.js:209
5573 msgid "No additional review rules set."
5574 msgstr ""
5575
5576 5619 #: rhodecode/templates/index.mako:5
5577 5620 msgid "Dashboard"
5578 5621 msgstr ""
5579 5622
5580 5623 #: rhodecode/templates/index_base.mako:21
5581 5624 msgid "No repositories or repositories groups exists here."
5582 5625 msgstr ""
5583 5626
5584 5627 #: rhodecode/templates/index_base.mako:80
5585 5628 #: rhodecode/templates/index_base.mako:167
5586 5629 #: rhodecode/templates/admin/gists/gist_index.mako:107
5587 5630 #: rhodecode/templates/admin/integrations/list.mako:72
5588 5631 #: rhodecode/templates/admin/my_account/my_account_repos.mako:28
5589 5632 #: rhodecode/templates/admin/my_account/my_account_user_group_membership.mako:39
5590 5633 #: rhodecode/templates/admin/my_account/my_account_watched.mako:28
5591 5634 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:74
5592 5635 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:16
5593 5636 #: rhodecode/templates/admin/repos/repos.mako:76
5594 5637 #: rhodecode/templates/admin/user_groups/user_groups.mako:76
5595 5638 #: rhodecode/templates/admin/users/user_edit_groups.mako:57
5596 5639 #: rhodecode/templates/base/perms_summary.mako:173
5597 5640 #: rhodecode/templates/base/perms_summary.mako:247
5598 5641 #: rhodecode/templates/bookmarks/bookmarks.mako:69
5599 5642 #: rhodecode/templates/branches/branches.mako:68
5600 5643 #: rhodecode/templates/files/files_browser_tree.mako:16
5601 5644 #: rhodecode/templates/tags/tags.mako:69
5602 5645 msgid "Name"
5603 5646 msgstr ""
5604 5647
5605 5648 #: rhodecode/templates/index_base.mako:89
5606 5649 #: rhodecode/templates/index_base.mako:176
5607 5650 #: rhodecode/templates/admin/gists/gist_index.mako:109
5608 5651 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:27
5609 5652 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:87
5610 5653 #: rhodecode/templates/admin/my_account/my_account_profile.mako:68
5611 5654 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:10
5612 5655 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:58
5613 5656 #: rhodecode/templates/admin/my_account/my_account_user_group_membership.mako:44
5614 5657 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:49
5615 5658 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:58
5616 5659 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:55
5617 5660 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:77
5618 5661 #: rhodecode/templates/admin/repos/repo_add_base.mako:83
5619 5662 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:30
5620 5663 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:170
5621 5664 #: rhodecode/templates/admin/repos/repos.mako:84
5622 5665 #: rhodecode/templates/admin/user_groups/user_group_add.mako:42
5623 5666 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:46
5624 5667 #: rhodecode/templates/admin/user_groups/user_groups.mako:78
5625 5668 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:32
5626 5669 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:91
5627 5670 #: rhodecode/templates/admin/users/user_edit_groups.mako:62
5628 5671 #: rhodecode/templates/admin/users/user_edit_ips.mako:17
5629 5672 #: rhodecode/templates/admin/users/user_edit_profile.mako:74
5630 5673 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:15
5631 5674 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:57
5632 5675 #: rhodecode/templates/base/issue_tracker_settings.mako:79
5633 #: rhodecode/templates/compare/compare_commits.mako:19
5676 #: rhodecode/templates/compare/compare_commits.mako:22
5634 5677 #: rhodecode/templates/email_templates/pull_request_review.mako:49
5635 5678 #: rhodecode/templates/email_templates/pull_request_review.mako:134
5636 5679 #: rhodecode/templates/email_templates/pull_request_update.mako:45
5637 5680 #: rhodecode/templates/email_templates/pull_request_update.mako:139
5638 5681 #: rhodecode/templates/forks/fork.mako:56
5639 5682 #: rhodecode/templates/forks/forks.mako:62
5640 5683 #: rhodecode/templates/pullrequests/pullrequest.mako:104
5641 5684 #: rhodecode/templates/pullrequests/pullrequest_show.mako:398
5642 5685 #: rhodecode/templates/summary/components.mako:159
5643 5686 #: rhodecode/templates/user_group/profile.mako:25
5644 5687 #: rhodecode/templates/users/user_profile.mako:59
5645 5688 msgid "Description"
5646 5689 msgstr ""
5647 5690
5648 5691 #: rhodecode/templates/index_base.mako:96
5649 5692 #: rhodecode/templates/index_base.mako:183
5650 5693 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:80
5651 5694 #: rhodecode/templates/admin/repos/repos.mako:91
5652 5695 msgid "Last Change"
5653 5696 msgstr ""
5654 5697
5655 5698 #: rhodecode/templates/index_base.mako:109
5656 5699 #: rhodecode/templates/index_base.mako:196
5657 5700 #: rhodecode/templates/admin/my_account/my_account_user_group_membership.mako:50
5658 5701 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:8
5659 5702 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:37
5660 5703 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:84
5661 5704 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:6
5662 5705 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:152
5663 5706 #: rhodecode/templates/admin/repos/repos.mako:104
5664 5707 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:6
5665 5708 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:28
5666 5709 #: rhodecode/templates/admin/user_groups/user_groups.mako:86
5667 5710 #: rhodecode/templates/admin/users/user_edit_groups.mako:68
5668 5711 #: rhodecode/templates/forks/forks.mako:58
5669 5712 #: rhodecode/templates/summary/components.mako:148
5670 5713 #: rhodecode/templates/user_group/profile.mako:35
5671 5714 msgid "Owner"
5672 5715 msgstr ""
5673 5716
5674 5717 #: rhodecode/templates/index_base.mako:190
5675 5718 #: rhodecode/templates/admin/repos/repos.mako:98
5676 5719 #: rhodecode/templates/bookmarks/bookmarks.mako:76
5677 5720 #: rhodecode/templates/branches/branches.mako:75
5678 #: rhodecode/templates/compare/compare_commits.mako:17
5721 #: rhodecode/templates/compare/compare_commits.mako:20
5679 5722 #: rhodecode/templates/email_templates/commit_comment.mako:60
5680 5723 #: rhodecode/templates/email_templates/commit_comment.mako:114
5681 5724 #: rhodecode/templates/email_templates/commit_comment.mako:141
5682 5725 #: rhodecode/templates/files/file_authors_box.mako:28
5683 5726 #: rhodecode/templates/pullrequests/pullrequest_show.mako:396
5684 5727 #: rhodecode/templates/search/search_commit.mako:9
5685 5728 #: rhodecode/templates/summary/components.mako:117
5686 5729 #: rhodecode/templates/summary/components.mako:125
5687 5730 #: rhodecode/templates/tags/tags.mako:76
5688 5731 msgid "Commit"
5689 5732 msgstr ""
5690 5733
5691 5734 #: rhodecode/templates/index_repo_group.mako:5
5692 5735 #, python-format
5693 5736 msgid "%s Repository group dashboard"
5694 5737 msgstr ""
5695 5738
5696 5739 #: rhodecode/templates/index_repo_group.mako:13
5697 5740 #: rhodecode/templates/base/base.mako:810
5698 5741 #: rhodecode/templates/base/base.mako:811
5699 5742 msgid "Home"
5700 5743 msgstr ""
5701 5744
5702 5745 #: rhodecode/templates/login.mako:5 rhodecode/templates/login.mako:91
5703 5746 #: rhodecode/templates/debug_style/login.html:60
5704 5747 msgid "Sign In"
5705 5748 msgstr ""
5706 5749
5707 5750 #: rhodecode/templates/login.mako:39
5708 5751 msgid "Sign In using username/password"
5709 5752 msgstr ""
5710 5753
5711 5754 #: rhodecode/templates/login.mako:53
5712 5755 msgid "Forgot your password?"
5713 5756 msgstr ""
5714 5757
5715 5758 #: rhodecode/templates/login.mako:66
5716 5759 msgid "Remember my indefinitely"
5717 5760 msgstr ""
5718 5761
5719 5762 #: rhodecode/templates/login.mako:68
5720 5763 msgid "Remember me for {}"
5721 5764 msgstr ""
5722 5765
5723 5766 #: rhodecode/templates/login.mako:74
5724 5767 msgid "Create a new account."
5725 5768 msgstr ""
5726 5769
5727 5770 #: rhodecode/templates/login.mako:81
5728 5771 msgid "Password reset is disabled."
5729 5772 msgstr ""
5730 5773
5731 5774 #: rhodecode/templates/login.mako:82
5732 5775 msgid "Please contact "
5733 5776 msgstr ""
5734 5777
5735 5778 #: rhodecode/templates/login.mako:84 rhodecode/templates/password_reset.mako:40
5736 5779 #: rhodecode/templates/base/base.mako:63
5737 5780 msgid "Support"
5738 5781 msgstr ""
5739 5782
5740 5783 #: rhodecode/templates/login.mako:85 rhodecode/templates/password_reset.mako:41
5741 5784 msgid "or"
5742 5785 msgstr ""
5743 5786
5744 5787 #: rhodecode/templates/login.mako:87 rhodecode/templates/password_reset.mako:43
5745 5788 msgid "an administrator if you need help."
5746 5789 msgstr ""
5747 5790
5748 5791 #: rhodecode/templates/login.mako:91
5749 5792 msgid "Sign in to {}"
5750 5793 msgstr ""
5751 5794
5752 5795 #: rhodecode/templates/password_reset.mako:5
5753 5796 msgid "Reset Password"
5754 5797 msgstr ""
5755 5798
5756 5799 #: rhodecode/templates/password_reset.mako:38
5757 5800 msgid "Password reset is disabled. Please contact "
5758 5801 msgstr ""
5759 5802
5760 5803 #: rhodecode/templates/password_reset.mako:50
5761 5804 msgid "Reset your Password"
5762 5805 msgstr ""
5763 5806
5764 5807 #: rhodecode/templates/password_reset.mako:51
5765 5808 msgid "Go to the login page to sign in."
5766 5809 msgstr ""
5767 5810
5768 5811 #: rhodecode/templates/password_reset.mako:55
5769 5812 msgid "Email Address"
5770 5813 msgstr ""
5771 5814
5772 5815 #: rhodecode/templates/password_reset.mako:61
5773 5816 msgid "Password reset link will be sent to matching email address"
5774 5817 msgstr ""
5775 5818
5776 5819 #: rhodecode/templates/password_reset.mako:65
5777 5820 #: rhodecode/templates/register.mako:106
5778 5821 msgid "Captcha"
5779 5822 msgstr ""
5780 5823
5781 5824 #: rhodecode/templates/password_reset.mako:76
5782 5825 msgid "Send password reset email"
5783 5826 msgstr ""
5784 5827
5785 5828 #: rhodecode/templates/register.mako:5
5786 5829 msgid "Create an Account"
5787 5830 msgstr ""
5788 5831
5789 5832 #: rhodecode/templates/register.mako:40
5790 5833 msgid "Create an account linked with {}"
5791 5834 msgstr ""
5792 5835
5793 5836 #: rhodecode/templates/register.mako:42
5794 5837 msgid "Create an account"
5795 5838 msgstr ""
5796 5839
5797 5840 #: rhodecode/templates/register.mako:45
5798 5841 msgid "Go to the login page to sign in with an existing account."
5799 5842 msgstr ""
5800 5843
5801 5844 #: rhodecode/templates/register.mako:71
5802 5845 msgid "Re-enter password"
5803 5846 msgstr ""
5804 5847
5805 5848 #: rhodecode/templates/register.mako:83
5806 5849 #: rhodecode/templates/admin/my_account/my_account_profile.mako:48
5807 5850 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:38
5808 5851 #: rhodecode/templates/admin/users/user_add.mako:68
5809 5852 #: rhodecode/templates/admin/users/user_edit_profile.mako:47
5810 5853 #: rhodecode/templates/admin/users/users.mako:78
5811 5854 msgid "First Name"
5812 5855 msgstr ""
5813 5856
5814 5857 #: rhodecode/templates/register.mako:90
5815 5858 #: rhodecode/templates/admin/my_account/my_account_profile.mako:58
5816 5859 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:47
5817 5860 #: rhodecode/templates/admin/users/user_add.mako:77
5818 5861 #: rhodecode/templates/admin/users/user_edit_profile.mako:56
5819 5862 #: rhodecode/templates/admin/users/users.mako:80
5820 5863 msgid "Last Name"
5821 5864 msgstr ""
5822 5865
5823 5866 #: rhodecode/templates/register.mako:118
5824 5867 msgid "Account activation requires admin approval."
5825 5868 msgstr ""
5826 5869
5827 5870 #: rhodecode/templates/register.mako:125
5828 5871 msgid "Create Account"
5829 5872 msgstr ""
5830 5873
5831 5874 #: rhodecode/templates/register.mako:125
5832 5875 msgid "Create Account in {}"
5833 5876 msgstr ""
5834 5877
5835 5878 #: rhodecode/templates/admin/admin_audit_log_entry.mako:6
5836 5879 msgid "Admin audit log entry"
5837 5880 msgstr ""
5838 5881
5839 5882 #: rhodecode/templates/admin/admin_audit_log_entry.mako:26
5840 5883 msgid "Audit long entry"
5841 5884 msgstr ""
5842 5885
5843 5886 #: rhodecode/templates/admin/admin_audit_log_entry.mako:34
5844 5887 #: rhodecode/templates/users/user.mako:4
5845 5888 msgid "User"
5846 5889 msgstr ""
5847 5890
5848 5891 #: rhodecode/templates/admin/admin_audit_log_entry.mako:46
5849 5892 #: rhodecode/templates/admin/admin_log_base.mako:11
5850 5893 #: rhodecode/templates/bookmarks/bookmarks.mako:71
5851 5894 #: rhodecode/templates/branches/branches.mako:70
5852 5895 #: rhodecode/templates/tags/tags.mako:71
5853 5896 msgid "Date"
5854 5897 msgstr ""
5855 5898
5856 5899 #: rhodecode/templates/admin/admin_audit_log_entry.mako:54
5857 5900 #: rhodecode/templates/admin/admin_log_base.mako:12
5858 5901 msgid "IP"
5859 5902 msgstr ""
5860 5903
5861 5904 #: rhodecode/templates/admin/admin_audit_log_entry.mako:63
5862 5905 #: rhodecode/templates/admin/admin_log_base.mako:8
5863 5906 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:31
5864 5907 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:13
5865 5908 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:55
5866 5909 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:86
5867 5910 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:13
5868 5911 #: rhodecode/templates/admin/repos/repos.mako:116
5869 5912 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:75
5870 5913 #: rhodecode/templates/admin/user_groups/user_groups.mako:88
5871 5914 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:36
5872 5915 #: rhodecode/templates/admin/users/user_edit_groups.mako:76
5873 5916 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:18
5874 5917 #: rhodecode/templates/admin/users/users.mako:91
5875 5918 #: rhodecode/templates/forks/forks.mako:69
5876 5919 msgid "Action"
5877 5920 msgstr ""
5878 5921
5879 5922 #: rhodecode/templates/admin/admin_audit_log_entry.mako:81
5880 5923 #: rhodecode/templates/admin/admin_log_base.mako:9
5881 5924 msgid "Action Data"
5882 5925 msgstr ""
5883 5926
5884 5927 #: rhodecode/templates/admin/admin_audit_log_entry.mako:89
5885 5928 #: rhodecode/templates/admin/admin_log_base.mako:47
5886 5929 msgid "data not available for v1 entries type"
5887 5930 msgstr ""
5888 5931
5889 5932 #: rhodecode/templates/admin/admin_audit_log_entry.mako:95
5890 5933 #: rhodecode/templates/admin/admin_log_base.mako:10
5891 5934 #: rhodecode/templates/admin/defaults/defaults.mako:32
5892 5935 #: rhodecode/templates/admin/permissions/permissions_objects.mako:16
5893 5936 #: rhodecode/templates/base/base.mako:658
5894 5937 #: rhodecode/templates/base/base.mako:660
5895 5938 #: rhodecode/templates/base/base.mako:662
5896 5939 #: rhodecode/templates/search/search_commit.mako:8
5897 5940 #: rhodecode/templates/search/search_path.mako:7
5898 5941 msgid "Repository"
5899 5942 msgstr ""
5900 5943
5901 5944 #: rhodecode/templates/admin/admin_audit_logs.mako:5
5902 5945 #: rhodecode/templates/base/base.mako:113
5903 5946 msgid "Admin audit logs"
5904 5947 msgstr ""
5905 5948
5906 5949 #: rhodecode/templates/admin/admin_audit_logs.mako:25
5907 5950 msgid "filter..."
5908 5951 msgstr ""
5909 5952
5910 5953 #: rhodecode/templates/admin/admin_audit_logs.mako:26
5911 5954 #: rhodecode/templates/admin/repos/repo_edit_audit.mako:15
5912 5955 #: rhodecode/templates/admin/users/user_edit_audit.mako:18
5913 5956 #: rhodecode/templates/journal/journal.mako:13
5914 5957 msgid "filter"
5915 5958 msgstr ""
5916 5959
5917 5960 #: rhodecode/templates/admin/admin_audit_logs.mako:27
5918 5961 #: rhodecode/templates/admin/repos/repo_edit.mako:91
5919 5962 #: rhodecode/templates/admin/users/user_edit.mako:48
5920 5963 msgid "Audit logs"
5921 5964 msgstr ""
5922 5965
5923 5966 #: rhodecode/templates/admin/admin_audit_logs.mako:29
5924 5967 #: rhodecode/templates/admin/repos/repo_edit_audit.mako:18
5925 5968 #: rhodecode/templates/admin/users/user_edit_audit.mako:21
5926 5969 #: rhodecode/templates/journal/journal.mako:16
5927 5970 msgid "Example Queries"
5928 5971 msgstr ""
5929 5972
5930 5973 #: rhodecode/templates/admin/admin_log_base.mako:6
5931 5974 msgid "Uid"
5932 5975 msgstr ""
5933 5976
5934 5977 #: rhodecode/templates/admin/admin_log_base.mako:42
5935 5978 msgid "toggle"
5936 5979 msgstr ""
5937 5980
5938 5981 #: rhodecode/templates/admin/admin_log_base.mako:68
5939 5982 msgid "No actions yet"
5940 5983 msgstr ""
5941 5984
5942 5985 #: rhodecode/templates/admin/main.mako:5
5943 5986 #: rhodecode/templates/admin/settings/settings.mako:5
5944 5987 msgid "Settings administration"
5945 5988 msgstr ""
5946 5989
5947 5990 #: rhodecode/templates/admin/main.mako:26
5948 5991 msgid "Administration area"
5949 5992 msgstr ""
5950 5993
5951 5994 #: rhodecode/templates/admin/main.mako:30
5952 5995 msgid "Repositories under administration"
5953 5996 msgstr ""
5954 5997
5955 5998 #: rhodecode/templates/admin/main.mako:34
5956 5999 #: rhodecode/templates/admin/repos/repo_add.mako:22
5957 6000 #: rhodecode/templates/admin/repos/repos.mako:31
5958 6001 msgid "Add Repository"
5959 6002 msgstr ""
5960 6003
5961 6004 #: rhodecode/templates/admin/main.mako:39
5962 6005 msgid "Repository groups under administration"
5963 6006 msgstr ""
5964 6007
5965 6008 #: rhodecode/templates/admin/main.mako:43
5966 6009 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:16
5967 6010 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:31
5968 6011 msgid "Add Repository Group"
5969 6012 msgstr ""
5970 6013
5971 6014 #: rhodecode/templates/admin/main.mako:48
5972 6015 msgid "User groups under administration"
5973 6016 msgstr ""
5974 6017
5975 6018 #: rhodecode/templates/admin/main.mako:52
5976 6019 #: rhodecode/templates/admin/user_groups/user_group_add.mako:15
5977 6020 #: rhodecode/templates/admin/user_groups/user_groups.mako:31
5978 6021 msgid "Add User Group"
5979 6022 msgstr ""
5980 6023
5981 6024 #: rhodecode/templates/admin/auth/auth_settings.mako:5
5982 6025 #: rhodecode/templates/admin/auth/plugin_settings.mako:5
5983 6026 msgid "Authentication Settings"
5984 6027 msgstr ""
5985 6028
5986 6029 #: rhodecode/templates/admin/auth/auth_settings.mako:42
5987 6030 msgid "Enabled and Available Plugins"
5988 6031 msgstr ""
5989 6032
5990 6033 #: rhodecode/templates/admin/auth/auth_settings.mako:48
5991 6034 msgid "Ordered Activated Plugins"
5992 6035 msgstr ""
5993 6036
5994 6037 #: rhodecode/templates/admin/auth/auth_settings.mako:53
5995 6038 msgid ""
5996 6039 "List of plugins, separated by commas.\n"
5997 6040 "The order of the plugins is also the order in which RhodeCode Enterprise will try to authenticate a user."
5998 6041 msgstr ""
5999 6042
6000 6043 #: rhodecode/templates/admin/auth/auth_settings.mako:60
6001 6044 msgid "Activate"
6002 6045 msgstr ""
6003 6046
6004 6047 #: rhodecode/templates/admin/auth/auth_settings.mako:61
6005 6048 msgid "Plugin Name"
6006 6049 msgstr ""
6007 6050
6008 6051 #: rhodecode/templates/admin/auth/auth_settings.mako:62
6009 6052 #: rhodecode/templates/base/base.mako:64
6010 6053 msgid "Documentation"
6011 6054 msgstr ""
6012 6055
6013 6056 #: rhodecode/templates/admin/auth/auth_settings.mako:63
6014 6057 msgid "Plugin ID"
6015 6058 msgstr ""
6016 6059
6017 6060 #: rhodecode/templates/admin/auth/auth_settings.mako:85
6018 6061 #: rhodecode/templates/admin/auth/plugin_settings.mako:96
6019 6062 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:63
6020 6063 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:109
6021 6064 #: rhodecode/templates/admin/permissions/permissions_application.mako:59
6022 6065 #: rhodecode/templates/admin/permissions/permissions_objects.mako:59
6023 6066 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:226
6024 6067 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:77
6025 6068 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:66
6026 6069 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:84
6027 6070 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:206
6028 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:250
6071 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:251
6029 6072 #: rhodecode/templates/admin/settings/settings_hooks.mako:63
6030 6073 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:15
6031 6074 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:216
6032 6075 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:106
6033 6076 #: rhodecode/templates/admin/users/user_edit_groups.mako:30
6034 6077 #: rhodecode/templates/admin/users/user_edit_profile.mako:154
6035 6078 #: rhodecode/templates/base/default_perms_box.mako:101
6036 6079 msgid "Save"
6037 6080 msgstr ""
6038 6081
6039 6082 #: rhodecode/templates/admin/auth/plugin_settings.mako:46
6040 6083 msgid "Plugin"
6041 6084 msgstr ""
6042 6085
6043 6086 #: rhodecode/templates/admin/defaults/defaults.mako:5
6044 6087 #: rhodecode/templates/admin/defaults/defaults.mako:14
6045 6088 msgid "Repositories defaults"
6046 6089 msgstr ""
6047 6090
6048 6091 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:3
6049 6092 msgid "Default Settings For New Repositories"
6050 6093 msgstr ""
6051 6094
6052 6095 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:14
6053 6096 #: rhodecode/templates/admin/gists/gist_index.mako:105
6054 6097 #: rhodecode/templates/admin/integrations/list.mako:73
6055 6098 #: rhodecode/templates/admin/repos/repo_add_base.mako:58
6056 6099 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:12
6057 6100 msgid "Type"
6058 6101 msgstr ""
6059 6102
6060 6103 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:23
6061 6104 #: rhodecode/templates/admin/repos/repo_add_base.mako:113
6062 6105 msgid "Private Repository"
6063 6106 msgstr ""
6064 6107
6065 6108 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:27
6066 6109 #: rhodecode/templates/admin/repos/repo_add_base.mako:117
6067 6110 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:199
6068 6111 #: rhodecode/templates/forks/fork.mako:92
6069 6112 msgid "Private repositories are only visible to people explicitly added as collaborators."
6070 6113 msgstr ""
6071 6114
6072 6115 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:34
6073 6116 msgid "Enable Statistics"
6074 6117 msgstr ""
6075 6118
6076 6119 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:38
6077 6120 msgid "Enable a statistics window on the repository summary page."
6078 6121 msgstr ""
6079 6122
6080 6123 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:44
6081 6124 msgid "Enable Downloads"
6082 6125 msgstr ""
6083 6126
6084 6127 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:48
6085 6128 msgid "Enable the download option on the repository summary page."
6086 6129 msgstr ""
6087 6130
6088 6131 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:54
6089 6132 msgid "Enable Locking"
6090 6133 msgstr ""
6091 6134
6092 6135 #: rhodecode/templates/admin/defaults/defaults_repositories.mako:58
6093 6136 msgid "Enable automatic repository locking. Pulling from a repository will lock it, and it is unlocked by pushing back by the same user."
6094 6137 msgstr ""
6095 6138
6096 6139 #: rhodecode/templates/admin/gists/gist_edit.mako:5
6097 6140 msgid "Edit Gist"
6098 6141 msgstr ""
6099 6142
6100 6143 #: rhodecode/templates/admin/gists/gist_edit.mako:33
6101 6144 #: rhodecode/templates/admin/gists/gist_new.mako:34
6102 6145 msgid "Gist lifetime"
6103 6146 msgstr ""
6104 6147
6105 6148 #: rhodecode/templates/admin/gists/gist_edit.mako:36
6106 6149 msgid "Gist access level"
6107 6150 msgstr ""
6108 6151
6109 6152 #: rhodecode/templates/admin/gists/gist_edit.mako:40
6110 6153 #: rhodecode/templates/admin/gists/gist_new.mako:40
6111 6154 msgid "Gist description ..."
6112 6155 msgstr ""
6113 6156
6114 6157 #: rhodecode/templates/admin/gists/gist_edit.mako:54
6115 6158 #: rhodecode/templates/admin/gists/gist_new.mako:48
6116 6159 #: rhodecode/templates/files/files_add.mako:66
6117 6160 #: rhodecode/templates/files/files_edit.mako:68
6118 6161 msgid "plain"
6119 6162 msgstr ""
6120 6163
6121 6164 #: rhodecode/templates/admin/gists/gist_edit.mako:99
6122 6165 msgid "Update Gist"
6123 6166 msgstr ""
6124 6167
6125 6168 #: rhodecode/templates/admin/gists/gist_edit.mako:100
6126 6169 #: rhodecode/templates/base/issue_tracker_settings.mako:151
6127 6170 #: rhodecode/templates/pullrequests/pullrequest_show.mako:82
6128 6171 msgid "Cancel"
6129 6172 msgstr ""
6130 6173
6131 6174 #: rhodecode/templates/admin/gists/gist_edit.mako:123
6132 6175 #, python-format
6133 6176 msgid "Gist was updated since you started editing. Copy your changes and click %(here)s to reload the new version."
6134 6177 msgstr ""
6135 6178
6136 6179 #: rhodecode/templates/admin/gists/gist_index.mako:6
6137 6180 msgid "Private Gists for user {}"
6138 6181 msgstr ""
6139 6182
6140 6183 #: rhodecode/templates/admin/gists/gist_index.mako:8
6141 6184 msgid "Public Gists for user {}"
6142 6185 msgstr ""
6143 6186
6144 6187 #: rhodecode/templates/admin/gists/gist_index.mako:10
6145 6188 msgid "Public Gists"
6146 6189 msgstr ""
6147 6190
6148 6191 #: rhodecode/templates/admin/gists/gist_index.mako:30
6149 6192 msgid "All gists"
6150 6193 msgstr ""
6151 6194
6152 6195 #: rhodecode/templates/admin/gists/gist_index.mako:32
6153 6196 msgid "All public"
6154 6197 msgstr ""
6155 6198
6156 6199 #: rhodecode/templates/admin/gists/gist_index.mako:34
6157 6200 msgid "My gists"
6158 6201 msgstr ""
6159 6202
6160 6203 #: rhodecode/templates/admin/gists/gist_index.mako:35
6161 6204 msgid "My private"
6162 6205 msgstr ""
6163 6206
6164 6207 #: rhodecode/templates/admin/gists/gist_index.mako:36
6165 6208 msgid "My public"
6166 6209 msgstr ""
6167 6210
6168 6211 #: rhodecode/templates/admin/gists/gist_index.mako:43
6169 6212 msgid "Create New Gist"
6170 6213 msgstr ""
6171 6214
6172 6215 #: rhodecode/templates/admin/gists/gist_index.mako:54
6173 6216 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:23
6174 6217 #: rhodecode/templates/admin/my_account/my_account_repos.mako:7
6175 6218 #: rhodecode/templates/admin/my_account/my_account_watched.mako:7
6176 6219 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:11
6177 6220 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:25
6178 6221 #: rhodecode/templates/admin/repos/repos.mako:25
6179 6222 #: rhodecode/templates/admin/user_groups/user_groups.mako:25
6180 6223 #: rhodecode/templates/admin/users/users.mako:26
6181 6224 #: rhodecode/templates/bookmarks/bookmarks.mako:39
6182 6225 #: rhodecode/templates/branches/branches.mako:39
6183 6226 #: rhodecode/templates/journal/journal.mako:12
6184 6227 #: rhodecode/templates/pullrequests/pullrequests.mako:53
6185 6228 #: rhodecode/templates/tags/tags.mako:39
6186 6229 msgid "quick filter..."
6187 6230 msgstr ""
6188 6231
6189 6232 #: rhodecode/templates/admin/gists/gist_index.mako:103
6190 6233 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:87
6191 6234 #: rhodecode/templates/bookmarks/bookmarks.mako:73
6192 6235 #: rhodecode/templates/branches/branches.mako:72
6193 6236 #: rhodecode/templates/commits/changelog.mako:138
6194 #: rhodecode/templates/compare/compare_commits.mako:16
6237 #: rhodecode/templates/compare/compare_commits.mako:19
6195 6238 #: rhodecode/templates/files/files_browser_tree.mako:20
6196 6239 #: rhodecode/templates/pullrequests/pullrequest_show.mako:395
6197 6240 #: rhodecode/templates/pullrequests/pullrequests.mako:100
6198 6241 #: rhodecode/templates/search/search_commit.mako:18
6199 6242 #: rhodecode/templates/summary/summary_commits.mako:11
6200 6243 #: rhodecode/templates/tags/tags.mako:73
6201 6244 msgid "Author"
6202 6245 msgstr ""
6203 6246
6204 6247 #: rhodecode/templates/admin/gists/gist_index.mako:111
6205 6248 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:11
6206 6249 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:51
6207 6250 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:9
6208 6251 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:7
6209 6252 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:7
6210 6253 #: rhodecode/templates/admin/users/user_edit_advanced.mako:6
6211 6254 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:16
6212 6255 #: rhodecode/templates/pullrequests/pullrequest_show.mako:58
6213 6256 msgid "Created on"
6214 6257 msgstr ""
6215 6258
6216 6259 #: rhodecode/templates/admin/gists/gist_index.mako:113
6217 6260 msgid "Expires"
6218 6261 msgstr ""
6219 6262
6220 6263 #: rhodecode/templates/admin/gists/gist_new.mako:5
6221 6264 #: rhodecode/templates/base/base.mako:577
6222 6265 msgid "New Gist"
6223 6266 msgstr ""
6224 6267
6225 6268 #: rhodecode/templates/admin/gists/gist_new.mako:31
6226 6269 msgid "Gist id"
6227 6270 msgstr ""
6228 6271
6229 6272 #: rhodecode/templates/admin/gists/gist_new.mako:32
6230 6273 msgid "Auto generated"
6231 6274 msgstr ""
6232 6275
6233 6276 #: rhodecode/templates/admin/gists/gist_new.mako:37
6234 6277 msgid "Private Gist access level"
6235 6278 msgstr ""
6236 6279
6237 6280 #: rhodecode/templates/admin/gists/gist_new.mako:47
6238 6281 msgid "name gist file..."
6239 6282 msgstr ""
6240 6283
6241 6284 #: rhodecode/templates/admin/gists/gist_new.mako:61
6242 6285 msgid "Create Gist"
6243 6286 msgstr ""
6244 6287
6245 6288 #: rhodecode/templates/admin/gists/gist_new.mako:69
6246 6289 #: rhodecode/templates/admin/gists/gist_new.mako:70
6247 6290 #: rhodecode/templates/data_table/_dt_elements.mako:352
6248 6291 msgid "Private Gist"
6249 6292 msgstr ""
6250 6293
6251 6294 #: rhodecode/templates/admin/gists/gist_new.mako:70
6252 6295 msgid "Private Gists are not listed and only accessible through their secret url."
6253 6296 msgstr ""
6254 6297
6255 6298 #: rhodecode/templates/admin/gists/gist_new.mako:73
6256 6299 #: rhodecode/templates/admin/gists/gist_new.mako:74
6257 6300 #: rhodecode/templates/data_table/_dt_elements.mako:350
6258 6301 msgid "Public Gist"
6259 6302 msgstr ""
6260 6303
6261 6304 #: rhodecode/templates/admin/gists/gist_new.mako:74
6262 6305 msgid "Public Gists are accessible to anyone and listed in Gists page."
6263 6306 msgstr ""
6264 6307
6265 6308 #: rhodecode/templates/admin/gists/gist_show.mako:14
6266 6309 #: rhodecode/templates/admin/gists/gist_show.mako:21
6267 6310 msgid "Gist"
6268 6311 msgstr ""
6269 6312
6270 6313 #: rhodecode/templates/admin/gists/gist_show.mako:41
6271 6314 msgid "Copy the url"
6272 6315 msgstr ""
6273 6316
6274 6317 #: rhodecode/templates/admin/gists/gist_show.mako:47
6275 6318 #: rhodecode/templates/files/files_source.mako:116
6276 6319 msgid "Copy content"
6277 6320 msgstr ""
6278 6321
6279 6322 #: rhodecode/templates/admin/gists/gist_show.mako:52
6280 6323 msgid "Show as Raw"
6281 6324 msgstr ""
6282 6325
6283 6326 #: rhodecode/templates/admin/gists/gist_show.mako:73
6284 6327 msgid "created"
6285 6328 msgstr ""
6286 6329
6287 6330 #: rhodecode/templates/admin/gists/gist_show.mako:74
6288 6331 msgid "expires"
6289 6332 msgstr ""
6290 6333
6291 6334 #: rhodecode/templates/admin/gists/gist_show.mako:95
6292 6335 #: rhodecode/templates/files/files_delete.mako:57
6293 6336 #: rhodecode/templates/files/files_source.mako:138
6294 6337 msgid "Show as raw"
6295 6338 msgstr ""
6296 6339
6297 6340 #: rhodecode/templates/admin/integrations/base.mako:14
6298 6341 msgid "Integrations Settings"
6299 6342 msgstr ""
6300 6343
6301 6344 #: rhodecode/templates/admin/integrations/base.mako:23
6302 6345 #: rhodecode/templates/admin/integrations/form.mako:8
6303 6346 #: rhodecode/templates/admin/integrations/form.mako:21
6304 6347 #: rhodecode/templates/admin/integrations/form.mako:32
6305 6348 #: rhodecode/templates/admin/integrations/global.mako:14
6306 6349 #: rhodecode/templates/admin/integrations/list.mako:21
6307 6350 #: rhodecode/templates/admin/integrations/list.mako:25
6308 6351 #: rhodecode/templates/admin/integrations/list.mako:29
6309 6352 #: rhodecode/templates/admin/integrations/list.mako:36
6310 6353 #: rhodecode/templates/admin/integrations/new.mako:15
6311 6354 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:33
6312 6355 #: rhodecode/templates/admin/repos/repo_edit.mako:74
6313 6356 #: rhodecode/templates/base/base.mako:120
6314 6357 msgid "Integrations"
6315 6358 msgstr ""
6316 6359
6317 6360 #: rhodecode/templates/admin/integrations/form.mako:30
6318 6361 #: rhodecode/templates/admin/integrations/list.mako:16
6319 6362 #: rhodecode/templates/admin/integrations/new.mako:7
6320 6363 #: rhodecode/templates/admin/integrations/new.mako:9
6321 6364 #: rhodecode/templates/admin/integrations/new.mako:13
6322 6365 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:30
6323 6366 #: rhodecode/templates/admin/repos/repo_edit.mako:15
6324 6367 #: rhodecode/templates/admin/repos/repo_edit.mako:42
6325 6368 #: rhodecode/templates/admin/settings/settings.mako:14
6326 6369 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:34
6327 6370 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:9
6328 6371 #: rhodecode/templates/base/base.mako:122
6329 6372 msgid "Settings"
6330 6373 msgstr ""
6331 6374
6332 6375 #: rhodecode/templates/admin/integrations/form.mako:60
6333 6376 #, python-format
6334 6377 msgid "Create New %(integration_type)s Integration"
6335 6378 msgstr ""
6336 6379
6337 6380 #: rhodecode/templates/admin/integrations/global.mako:5
6338 6381 msgid "Integrations administration"
6339 6382 msgstr ""
6340 6383
6341 6384 #: rhodecode/templates/admin/integrations/list.mako:44
6342 6385 msgid "Current Integrations for Repository: {repo_name}"
6343 6386 msgstr ""
6344 6387
6345 6388 #: rhodecode/templates/admin/integrations/list.mako:46
6346 6389 msgid "Repository Group Integrations: {}"
6347 6390 msgstr ""
6348 6391
6349 6392 #: rhodecode/templates/admin/integrations/list.mako:48
6350 6393 msgid "Current Integrations"
6351 6394 msgstr ""
6352 6395
6353 6396 #: rhodecode/templates/admin/integrations/list.mako:65
6354 6397 #: rhodecode/templates/admin/integrations/new.mako:18
6355 6398 msgid "Create new integration"
6356 6399 msgstr ""
6357 6400
6358 6401 #: rhodecode/templates/admin/integrations/list.mako:74
6359 6402 msgid "Scope"
6360 6403 msgstr ""
6361 6404
6362 6405 #: rhodecode/templates/admin/integrations/list.mako:75
6363 6406 #: rhodecode/templates/compare/compare_diff.mako:84
6364 6407 msgid "Actions"
6365 6408 msgstr ""
6366 6409
6367 6410 #: rhodecode/templates/admin/integrations/list.mako:85
6368 6411 msgid "No {type} integrations for repo {repo} exist yet."
6369 6412 msgstr ""
6370 6413
6371 6414 #: rhodecode/templates/admin/integrations/list.mako:87
6372 6415 msgid "No {type} integrations for repogroup {repogroup} exist yet."
6373 6416 msgstr ""
6374 6417
6375 6418 #: rhodecode/templates/admin/integrations/list.mako:89
6376 6419 msgid "No {type} integrations exist yet."
6377 6420 msgstr ""
6378 6421
6379 6422 #: rhodecode/templates/admin/integrations/list.mako:103
6380 6423 msgid "Create one"
6381 6424 msgstr ""
6382 6425
6383 6426 #: rhodecode/templates/admin/integrations/list.mako:132
6384 6427 #: rhodecode/templates/hovercards/hovercard_pull_request.mako:19
6385 6428 msgid "repo"
6386 6429 msgstr ""
6387 6430
6388 6431 #: rhodecode/templates/admin/integrations/list.mako:136
6389 6432 msgid "repogroup"
6390 6433 msgstr ""
6391 6434
6392 6435 #: rhodecode/templates/admin/integrations/list.mako:138
6393 6436 msgid "child repos only"
6394 6437 msgstr ""
6395 6438
6396 6439 #: rhodecode/templates/admin/integrations/list.mako:140
6397 6440 msgid "cascade to all"
6398 6441 msgstr ""
6399 6442
6400 6443 #: rhodecode/templates/admin/integrations/list.mako:145
6401 6444 msgid "top level repos only"
6402 6445 msgstr ""
6403 6446
6404 6447 #: rhodecode/templates/admin/integrations/list.mako:147
6405 6448 msgid "global"
6406 6449 msgstr ""
6407 6450
6408 6451 #: rhodecode/templates/admin/integrations/list.mako:153
6409 6452 msgid "unknown integration"
6410 6453 msgstr ""
6411 6454
6412 6455 #: rhodecode/templates/admin/integrations/new.mako:23
6413 6456 msgid "Create New Integration for repository: {repo_name}"
6414 6457 msgstr ""
6415 6458
6416 6459 #: rhodecode/templates/admin/integrations/new.mako:25
6417 6460 msgid "Create New Integration for repository group: {repo_group_name}"
6418 6461 msgstr ""
6419 6462
6420 6463 #: rhodecode/templates/admin/integrations/new.mako:27
6421 6464 msgid "Create New Global Integration"
6422 6465 msgstr ""
6423 6466
6424 6467 #: rhodecode/templates/admin/integrations/new.mako:55
6425 6468 msgid "No description available"
6426 6469 msgstr ""
6427 6470
6428 6471 #: rhodecode/templates/admin/my_account/my_account.mako:5
6429 6472 #: rhodecode/templates/base/base.mako:622
6430 6473 msgid "My account"
6431 6474 msgstr ""
6432 6475
6433 6476 #: rhodecode/templates/admin/my_account/my_account.mako:12
6434 6477 msgid "My Account"
6435 6478 msgstr ""
6436 6479
6437 6480 #: rhodecode/templates/admin/my_account/my_account.mako:29
6438 6481 #: rhodecode/templates/email_templates/user_registration.mako:55
6439 6482 msgid "Profile"
6440 6483 msgstr ""
6441 6484
6442 6485 #: rhodecode/templates/admin/my_account/my_account.mako:30
6443 6486 #: rhodecode/templates/admin/users/user_edit.mako:45
6444 6487 #: rhodecode/templates/debug_style/index.html:55
6445 6488 msgid "Emails"
6446 6489 msgstr ""
6447 6490
6448 6491 #: rhodecode/templates/admin/my_account/my_account.mako:33
6449 6492 msgid "Auth Tokens"
6450 6493 msgstr ""
6451 6494
6452 6495 #: rhodecode/templates/admin/my_account/my_account.mako:34
6453 6496 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:3
6454 6497 #: rhodecode/templates/admin/permissions/permissions.mako:51
6455 6498 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:4
6456 6499 #: rhodecode/templates/admin/users/user_edit.mako:41
6457 6500 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:7
6458 6501 msgid "SSH Keys"
6459 6502 msgstr ""
6460 6503
6461 6504 #: rhodecode/templates/admin/my_account/my_account.mako:35
6462 6505 #: rhodecode/templates/admin/my_account/my_account_user_group_membership.mako:5
6463 6506 msgid "User Group Membership"
6464 6507 msgstr ""
6465 6508
6466 6509 #: rhodecode/templates/admin/my_account/my_account.mako:40
6467 6510 msgid "External Identities"
6468 6511 msgstr ""
6469 6512
6470 6513 #: rhodecode/templates/admin/my_account/my_account.mako:43
6471 6514 msgid "Owned Repositories"
6472 6515 msgstr ""
6473 6516
6474 6517 #: rhodecode/templates/admin/my_account/my_account.mako:44
6475 6518 msgid "Watched Repositories"
6476 6519 msgstr ""
6477 6520
6478 6521 #: rhodecode/templates/admin/my_account/my_account.mako:45
6479 6522 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:42
6480 6523 #: rhodecode/templates/base/base.mako:385
6481 6524 #: rhodecode/templates/base/base.mako:626
6482 6525 msgid "Pull Requests"
6483 6526 msgstr ""
6484 6527
6485 6528 #: rhodecode/templates/admin/my_account/my_account.mako:46
6486 6529 #: rhodecode/templates/admin/permissions/permissions.mako:14
6487 6530 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:35
6488 6531 #: rhodecode/templates/base/base.mako:118
6489 6532 msgid "Permissions"
6490 6533 msgstr ""
6491 6534
6492 6535 #: rhodecode/templates/admin/my_account/my_account.mako:47
6493 6536 msgid "Live Notifications"
6494 6537 msgstr ""
6495 6538
6496 6539 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:9
6497 6540 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:13
6498 6541 msgid "Authentication Tokens"
6499 6542 msgstr ""
6500 6543
6501 6544 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:14
6502 6545 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:19
6503 6546 msgid "Available roles"
6504 6547 msgstr ""
6505 6548
6506 6549 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:26
6507 6550 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:31
6508 6551 msgid "Token"
6509 6552 msgstr ""
6510 6553
6511 6554 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:29
6512 6555 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:34
6513 6556 msgid "Repository Scope"
6514 6557 msgstr ""
6515 6558
6516 6559 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:30
6517 6560 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:35
6518 6561 msgid "Expiration"
6519 6562 msgstr ""
6520 6563
6521 6564 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:72
6522 6565 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:76
6523 6566 msgid "No additional auth tokens specified"
6524 6567 msgstr ""
6525 6568
6526 6569 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:84
6527 6570 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:88
6528 6571 msgid "New authentication token"
6529 6572 msgstr ""
6530 6573
6531 6574 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:98
6532 6575 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:102
6533 6576 msgid "Repository scope works only with tokens with VCS type."
6534 6577 msgstr ""
6535 6578
6536 6579 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:102
6537 6580 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:72
6538 6581 #: rhodecode/templates/admin/permissions/permissions_ips.mako:63
6539 6582 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:65
6540 6583 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:106
6541 6584 #: rhodecode/templates/admin/users/user_edit_emails.mako:65
6542 6585 #: rhodecode/templates/admin/users/user_edit_ips.mako:75
6543 6586 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:71
6544 6587 msgid "Add"
6545 6588 msgstr ""
6546 6589
6547 6590 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:135
6548 6591 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:140
6549 6592 msgid "Select or enter expiration date"
6550 6593 msgstr ""
6551 6594
6552 6595 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.mako:170
6553 6596 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:175
6554 6597 msgid "repository scope"
6555 6598 msgstr ""
6556 6599
6557 6600 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:7
6558 6601 msgid "Position"
6559 6602 msgstr ""
6560 6603
6561 6604 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:17
6562 6605 msgid "Bookmark title (max 30 characters, optional)"
6563 6606 msgstr ""
6564 6607
6565 6608 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:24
6566 6609 msgid "Clear"
6567 6610 msgstr ""
6568 6611
6569 6612 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:30
6570 6613 msgid "Server URL is available as ${server_url} variable. E.g. Redirect url: ${server_url}/_admin/exception_tracker"
6571 6614 msgstr ""
6572 6615
6573 6616 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:34
6574 6617 msgid "Redirect URL"
6575 6618 msgstr ""
6576 6619
6577 6620 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:44
6578 6621 msgid "Repository template"
6579 6622 msgstr ""
6580 6623
6581 6624 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:50
6582 6625 msgid "Repository group template"
6583 6626 msgstr ""
6584 6627
6585 6628 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:56
6586 6629 msgid "Template Repository or Repository group"
6587 6630 msgstr ""
6588 6631
6589 6632 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:66
6590 6633 msgid "Available as ${repo_url} e.g. Redirect url: ${repo_url}/changelog"
6591 6634 msgstr ""
6592 6635
6593 6636 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:68
6594 6637 msgid "Available as ${repo_group_url} e.g. Redirect url: ${repo_group_url}"
6595 6638 msgstr ""
6596 6639
6597 6640 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:70
6598 6641 msgid "Available as full url variables in redirect url. i.e: ${repo_url}, ${repo_group_url}."
6599 6642 msgstr ""
6600 6643
6601 6644 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:80
6602 6645 msgid "Your Bookmarks"
6603 6646 msgstr ""
6604 6647
6605 6648 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:85
6606 6649 msgid "Store upto 10 bookmark links to favorite repositories, external issue tracker or CI server. "
6607 6650 msgstr ""
6608 6651
6609 6652 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:87
6610 6653 msgid "Bookmarks are accessible from your username dropdown or by keyboard shortcut `g 0-9`"
6611 6654 msgstr ""
6612 6655
6613 6656 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:141
6614 6657 msgid "repository"
6615 6658 msgstr ""
6616 6659
6617 6660 #: rhodecode/templates/admin/my_account/my_account_bookmarks.mako:192
6618 6661 msgid "repository group"
6619 6662 msgstr ""
6620 6663
6621 6664 #: rhodecode/templates/admin/my_account/my_account_emails.mako:5
6622 6665 msgid "Account Emails"
6623 6666 msgstr ""
6624 6667
6625 6668 #: rhodecode/templates/admin/my_account/my_account_emails.mako:17
6626 6669 #: rhodecode/templates/admin/users/user_edit_emails.mako:19
6627 6670 msgid "Primary"
6628 6671 msgstr ""
6629 6672
6630 6673 #: rhodecode/templates/admin/my_account/my_account_emails.mako:43
6631 6674 #: rhodecode/templates/admin/users/user_edit_emails.mako:44
6632 6675 msgid "No additional emails specified"
6633 6676 msgstr ""
6634 6677
6635 6678 #: rhodecode/templates/admin/my_account/my_account_emails.mako:52
6636 6679 #: rhodecode/templates/admin/my_account/my_account_password.mako:6
6637 6680 msgid "Your user account details are managed by an external source. Details cannot be managed here."
6638 6681 msgstr ""
6639 6682
6640 6683 #: rhodecode/templates/admin/my_account/my_account_emails.mako:53
6641 6684 #: rhodecode/templates/admin/my_account/my_account_password.mako:9
6642 6685 msgid "Source type"
6643 6686 msgstr ""
6644 6687
6645 6688 #: rhodecode/templates/admin/my_account/my_account_notifications.mako:4
6646 6689 msgid "Your Live Notification Settings"
6647 6690 msgstr ""
6648 6691
6649 6692 #: rhodecode/templates/admin/my_account/my_account_notifications.mako:16
6650 6693 msgid "Notifications Status"
6651 6694 msgstr ""
6652 6695
6653 6696 #: rhodecode/templates/admin/my_account/my_account_notifications.mako:40
6654 6697 msgid "Test Notifications"
6655 6698 msgstr ""
6656 6699
6657 6700 #: rhodecode/templates/admin/my_account/my_account_password.mako:3
6658 6701 msgid "Change Your Account Password"
6659 6702 msgstr ""
6660 6703
6661 6704 #: rhodecode/templates/admin/my_account/my_account_password.mako:7
6662 6705 #: rhodecode/templates/admin/my_account/my_account_profile.mako:18
6663 6706 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:19
6664 6707 msgid "For VCS access please generate"
6665 6708 msgstr ""
6666 6709
6667 6710 #: rhodecode/templates/admin/my_account/my_account_profile.mako:6
6668 6711 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:4
6669 6712 msgid "My Profile"
6670 6713 msgstr ""
6671 6714
6672 6715 #: rhodecode/templates/admin/my_account/my_account_profile.mako:15
6673 6716 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:16
6674 6717 #: rhodecode/templates/admin/users/user_edit_profile.mako:19
6675 6718 #, python-format
6676 6719 msgid "This user was created from external source (%s). Editing some of the settings is limited."
6677 6720 msgstr ""
6678 6721
6679 6722 #: rhodecode/templates/admin/my_account/my_account_profile.mako:24
6680 6723 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:60
6681 6724 #: rhodecode/templates/admin/users/user_edit_profile.mako:26
6682 6725 #: rhodecode/templates/users/user_profile.mako:15
6683 6726 msgid "Photo"
6684 6727 msgstr ""
6685 6728
6686 6729 #: rhodecode/templates/admin/my_account/my_account_profile.mako:82
6687 6730 #: rhodecode/templates/users/user_profile.mako:73
6688 6731 msgid "Missing email, please update your user email address."
6689 6732 msgstr ""
6690 6733
6691 6734 #: rhodecode/templates/admin/my_account/my_account_profile_edit.mako:65
6692 6735 msgid "Change your avatar at"
6693 6736 msgstr ""
6694 6737
6695 6738 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:13
6696 6739 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:40
6697 6740 msgid "All"
6698 6741 msgstr ""
6699 6742
6700 6743 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:14
6701 6744 msgid "All + Closed"
6702 6745 msgstr ""
6703 6746
6704 6747 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:33
6705 6748 msgid "Pull Requests You Participate In"
6706 6749 msgstr ""
6707 6750
6708 6751 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:75
6709 6752 #: rhodecode/templates/pullrequests/pullrequests.mako:96
6710 6753 msgid "Id"
6711 6754 msgstr ""
6712 6755
6713 6756 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:81
6714 6757 #: rhodecode/templates/admin/settings/settings_global.mako:9
6715 6758 #: rhodecode/templates/email_templates/pull_request_review.mako:47
6716 6759 #: rhodecode/templates/email_templates/pull_request_update.mako:43
6717 6760 #: rhodecode/templates/pullrequests/pullrequest.mako:91
6718 6761 #: rhodecode/templates/pullrequests/pullrequests.mako:98
6719 6762 msgid "Title"
6720 6763 msgstr ""
6721 6764
6722 6765 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:99
6723 6766 #: rhodecode/templates/pullrequests/pullrequests.mako:104
6724 6767 msgid "Last Update"
6725 6768 msgstr ""
6726 6769
6727 6770 #: rhodecode/templates/admin/my_account/my_account_pullrequests.mako:105
6728 6771 msgid "Target Repo"
6729 6772 msgstr ""
6730 6773
6731 6774 #: rhodecode/templates/admin/my_account/my_account_repos.mako:3
6732 6775 msgid "Repositories You Own"
6733 6776 msgstr ""
6734 6777
6735 6778 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:9
6736 6779 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:47
6737 6780 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:14
6738 6781 msgid "Fingerprint"
6739 6782 msgstr ""
6740 6783
6741 6784 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:12
6742 6785 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:53
6743 6786 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:17
6744 6787 msgid "Accessed on"
6745 6788 msgstr ""
6746 6789
6747 6790 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:16
6748 6791 msgid "SSH Keys usage is currently disabled, please ask your administrator to enable them."
6749 6792 msgstr ""
6750 6793
6751 6794 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:41
6752 6795 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:42
6753 6796 msgid "No additional ssh keys specified"
6754 6797 msgstr ""
6755 6798
6756 6799 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:55
6757 6800 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:54
6758 6801 msgid "New ssh key"
6759 6802 msgstr ""
6760 6803
6761 6804 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:60
6762 6805 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:59
6763 6806 msgid "Generate random RSA key"
6764 6807 msgstr ""
6765 6808
6766 6809 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:67
6767 6810 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:66
6768 6811 msgid "Public key, begins with 'ssh-rsa', 'ssh-dss', 'ssh-ed25519', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', or 'ecdsa-sha2-nistp521'"
6769 6812 msgstr ""
6770 6813
6771 6814 #: rhodecode/templates/admin/my_account/my_account_ssh_keys.mako:76
6772 6815 msgid "Click add to use this generated SSH key"
6773 6816 msgstr ""
6774 6817
6775 6818 #: rhodecode/templates/admin/my_account/my_account_user_group_membership.mako:49
6776 6819 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:51
6777 6820 #: rhodecode/templates/admin/user_groups/user_group_add.mako:51
6778 6821 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:55
6779 6822 #: rhodecode/templates/admin/user_groups/user_groups.mako:84
6780 6823 #: rhodecode/templates/admin/users/user_add.mako:97
6781 6824 #: rhodecode/templates/admin/users/user_edit_groups.mako:67
6782 6825 #: rhodecode/templates/admin/users/user_edit_profile.mako:110
6783 6826 #: rhodecode/templates/admin/users/users.mako:85
6784 6827 #: rhodecode/templates/user_group/profile.mako:44
6785 6828 msgid "Active"
6786 6829 msgstr ""
6787 6830
6788 6831 #: rhodecode/templates/admin/my_account/my_account_watched.mako:3
6789 6832 msgid "Your Watched Repositories"
6790 6833 msgstr ""
6791 6834
6792 6835 #: rhodecode/templates/admin/notifications/notifications_data.mako:5
6793 6836 msgid "My notifications"
6794 6837 msgstr ""
6795 6838
6796 6839 #: rhodecode/templates/admin/notifications/notifications_data.mako:26
6797 6840 msgid "Mark as read"
6798 6841 msgstr ""
6799 6842
6800 6843 #: rhodecode/templates/admin/notifications/notifications_data.mako:42
6801 6844 msgid "No notifications here yet"
6802 6845 msgstr ""
6803 6846
6804 6847 #: rhodecode/templates/admin/notifications/notifications_show.mako:5
6805 6848 #: rhodecode/templates/admin/notifications/notifications_show.mako:14
6806 6849 #: rhodecode/templates/debug_style/email_plain_rendered.mako:5
6807 6850 #: rhodecode/templates/debug_style/email_plain_rendered.mako:14
6808 6851 msgid "Show notification"
6809 6852 msgstr ""
6810 6853
6811 6854 #: rhodecode/templates/admin/notifications/notifications_show.mako:12
6812 6855 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:5
6813 6856 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:20
6814 6857 #: rhodecode/templates/debug_style/email_plain_rendered.mako:12
6815 6858 msgid "My Notifications"
6816 6859 msgstr ""
6817 6860
6818 6861 #: rhodecode/templates/admin/notifications/notifications_show.mako:42
6819 6862 #: rhodecode/templates/debug_style/email_plain_rendered.mako:38
6820 6863 msgid "Subject"
6821 6864 msgstr ""
6822 6865
6823 6866 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:25
6824 6867 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:29
6825 6868 msgid "Mark all as read"
6826 6869 msgstr ""
6827 6870
6828 6871 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:39
6829 6872 msgid "Unread"
6830 6873 msgstr ""
6831 6874
6832 6875 #: rhodecode/templates/admin/notifications/notifications_show_all.mako:41
6833 6876 #: rhodecode/templates/changeset/changeset.mako:252
6834 6877 #: rhodecode/templates/changeset/changeset.mako:262
6835 #: rhodecode/templates/pullrequests/pullrequest_show.mako:763
6836 #: rhodecode/templates/pullrequests/pullrequest_show.mako:773
6878 #: rhodecode/templates/pullrequests/pullrequest_show.mako:759
6879 #: rhodecode/templates/pullrequests/pullrequest_show.mako:769
6837 6880 msgid "Comments"
6838 6881 msgstr ""
6839 6882
6840 6883 #: rhodecode/templates/admin/permissions/permissions.mako:5
6841 6884 msgid "Permissions Administration"
6842 6885 msgstr ""
6843 6886
6844 6887 #: rhodecode/templates/admin/permissions/permissions.mako:33
6845 6888 msgid "Application"
6846 6889 msgstr ""
6847 6890
6848 6891 #: rhodecode/templates/admin/permissions/permissions.mako:39
6849 6892 msgid "Object"
6850 6893 msgstr ""
6851 6894
6852 6895 #: rhodecode/templates/admin/permissions/permissions.mako:45
6853 6896 msgid "IP Whitelist"
6854 6897 msgstr ""
6855 6898
6856 6899 #: rhodecode/templates/admin/permissions/permissions.mako:48
6857 6900 msgid "AuthToken Access"
6858 6901 msgstr ""
6859 6902
6860 6903 #: rhodecode/templates/admin/permissions/permissions.mako:54
6861 6904 msgid "Overview"
6862 6905 msgstr ""
6863 6906
6864 6907 #: rhodecode/templates/admin/permissions/permissions_application.mako:3
6865 6908 msgid "System Wide Application Permissions"
6866 6909 msgstr ""
6867 6910
6868 6911 #: rhodecode/templates/admin/permissions/permissions_application.mako:12
6869 6912 msgid "Anonymous Access"
6870 6913 msgstr ""
6871 6914
6872 6915 #: rhodecode/templates/admin/permissions/permissions_application.mako:18
6873 6916 #, python-format
6874 6917 msgid "Allow access to RhodeCode Enterprise without requiring users to login. Anonymous users get the %s permission settings."
6875 6918 msgstr ""
6876 6919
6877 6920 #: rhodecode/templates/admin/permissions/permissions_application.mako:24
6878 6921 msgid "Registration"
6879 6922 msgstr ""
6880 6923
6881 6924 #: rhodecode/templates/admin/permissions/permissions_application.mako:33
6882 6925 msgid "Password Reset"
6883 6926 msgstr ""
6884 6927
6885 6928 #: rhodecode/templates/admin/permissions/permissions_application.mako:42
6886 6929 msgid "Registration Page Message"
6887 6930 msgstr ""
6888 6931
6889 6932 #: rhodecode/templates/admin/permissions/permissions_application.mako:46
6890 6933 msgid "Custom message to be displayed on the registration page. HTML syntax is supported."
6891 6934 msgstr ""
6892 6935
6893 6936 #: rhodecode/templates/admin/permissions/permissions_application.mako:52
6894 6937 msgid "External Authentication Account Activation"
6895 6938 msgstr ""
6896 6939
6897 6940 #: rhodecode/templates/admin/permissions/permissions_auth_token_access.mako:5
6898 6941 msgid "View whitelist"
6899 6942 msgstr ""
6900 6943
6901 6944 #: rhodecode/templates/admin/permissions/permissions_auth_token_access.mako:36
6902 6945 msgid "List of views available for usage in whitelist access"
6903 6946 msgstr ""
6904 6947
6905 6948 #: rhodecode/templates/admin/permissions/permissions_branch.mako:3
6906 6949 msgid "Default Permissions for Branches."
6907 6950 msgstr ""
6908 6951
6909 6952 #: rhodecode/templates/admin/permissions/permissions_branch.mako:6
6910 6953 #: rhodecode/templates/admin/repos/repo_edit_automation.mako:6
6911 6954 #: rhodecode/templates/admin/repos/repo_edit_permissions_branch.mako:6
6912 6955 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:67
6913 6956 #: rhodecode/templates/admin/repos/repo_edit_reviewers.mako:6
6914 6957 #: rhodecode/templates/admin/settings/settings_automation.mako:6
6915 6958 #: rhodecode/templates/artifacts/artifact_list.mako:24
6916 6959 #: rhodecode/templates/pullrequests/pullrequest.mako:197
6917 6960 msgid "This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license."
6918 6961 msgstr ""
6919 6962
6920 6963 #: rhodecode/templates/admin/permissions/permissions_ips.mako:5
6921 6964 msgid "Default IP Whitelist For All Users"
6922 6965 msgstr ""
6923 6966
6924 6967 #: rhodecode/templates/admin/permissions/permissions_ips.mako:9
6925 6968 #: rhodecode/templates/admin/users/user_edit_ips.mako:12
6926 6969 msgid "Current IP address"
6927 6970 msgstr ""
6928 6971
6929 6972 #: rhodecode/templates/admin/permissions/permissions_ips.mako:30
6930 6973 #: rhodecode/templates/admin/users/user_edit_ips.mako:41
6931 6974 #, python-format
6932 6975 msgid "Confirm to delete this ip: %s"
6933 6976 msgstr ""
6934 6977
6935 6978 #: rhodecode/templates/admin/permissions/permissions_ips.mako:37
6936 6979 #: rhodecode/templates/admin/users/user_edit_ips.mako:49
6937 6980 msgid "All IP addresses are allowed"
6938 6981 msgstr ""
6939 6982
6940 6983 #: rhodecode/templates/admin/permissions/permissions_ips.mako:52
6941 6984 #: rhodecode/templates/admin/users/user_edit_ips.mako:65
6942 6985 msgid "New IP Address"
6943 6986 msgstr ""
6944 6987
6945 6988 #: rhodecode/templates/admin/permissions/permissions_ips.mako:56
6946 6989 #: rhodecode/templates/admin/users/user_edit_ips.mako:68
6947 6990 msgid "Description..."
6948 6991 msgstr ""
6949 6992
6950 6993 #: rhodecode/templates/admin/permissions/permissions_ips.mako:57
6951 6994 msgid ""
6952 6995 "Enter a comma separated list of IP Addresses like 127.0.0.1,\n"
6953 6996 "or use an IP Address with a mask 127.0.0.1/24, to create a network range.\n"
6954 6997 "To specify multiple addresses in a range, use the 127.0.0.1-127.0.0.10 syntax"
6955 6998 msgstr ""
6956 6999
6957 7000 #: rhodecode/templates/admin/permissions/permissions_objects.mako:3
6958 7001 msgid "Default Permissions for Repositories, User Groups and Repository Groups."
6959 7002 msgstr ""
6960 7003
6961 7004 #: rhodecode/templates/admin/permissions/permissions_objects.mako:7
6962 7005 msgid "Default access permissions. This defines permissions for the `default` user from which other users inherit permissions."
6963 7006 msgstr ""
6964 7007
6965 7008 #: rhodecode/templates/admin/permissions/permissions_objects.mako:9
6966 7009 msgid "Check the overwrite checkbox to force change all previously defined permissions for `default` user to the new selected value."
6967 7010 msgstr ""
6968 7011
6969 7012 #: rhodecode/templates/admin/permissions/permissions_objects.mako:23
6970 7013 msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
6971 7014 msgstr ""
6972 7015
6973 7016 #: rhodecode/templates/admin/permissions/permissions_objects.mako:24
6974 7017 #: rhodecode/templates/admin/permissions/permissions_objects.mako:38
6975 7018 #: rhodecode/templates/admin/permissions/permissions_objects.mako:52
6976 7019 msgid "Overwrite Existing Settings"
6977 7020 msgstr ""
6978 7021
6979 7022 #: rhodecode/templates/admin/permissions/permissions_objects.mako:37
6980 7023 msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
6981 7024 msgstr ""
6982 7025
6983 7026 #: rhodecode/templates/admin/permissions/permissions_objects.mako:45
6984 7027 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:14
6985 7028 msgid "User Groups"
6986 7029 msgstr ""
6987 7030
6988 7031 #: rhodecode/templates/admin/permissions/permissions_objects.mako:51
6989 7032 msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost"
6990 7033 msgstr ""
6991 7034
6992 7035 #: rhodecode/templates/admin/permissions/permissions_perms.mako:1
6993 7036 msgid "Default User Permissions Overview"
6994 7037 msgstr ""
6995 7038
6996 7039 #: rhodecode/templates/admin/permissions/permissions_ssh_keys.mako:7
6997 7040 msgid "Update SSH keys file"
6998 7041 msgstr ""
6999 7042
7000 7043 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:5
7001 7044 msgid "Add repository group"
7002 7045 msgstr ""
7003 7046
7004 7047 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:14
7005 7048 #: rhodecode/templates/admin/users/user_edit_advanced.mako:13
7006 7049 #: rhodecode/templates/base/base.mako:115
7007 7050 #: rhodecode/templates/base/base.mako:136
7008 7051 msgid "Repository groups"
7009 7052 msgstr ""
7010 7053
7011 7054 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:35
7012 7055 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:15
7013 7056 #: rhodecode/templates/admin/user_groups/user_group_add.mako:34
7014 7057 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:19
7015 7058 msgid "Group name"
7016 7059 msgstr ""
7017 7060
7018 7061 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:44
7019 7062 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:25
7020 7063 #: rhodecode/templates/admin/repos/repo_add_base.mako:43
7021 7064 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:33
7022 7065 #: rhodecode/templates/base/base.mako:671
7023 7066 #: rhodecode/templates/data_table/_dt_elements.mako:217
7024 7067 #: rhodecode/templates/forks/fork.mako:41
7025 7068 msgid "Repository group"
7026 7069 msgstr ""
7027 7070
7028 7071 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:50
7029 7072 #: rhodecode/templates/admin/repos/repo_add_base.mako:49
7030 7073 msgid "Select my personal group ({})"
7031 7074 msgstr ""
7032 7075
7033 7076 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:65
7034 7077 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:64
7035 7078 #: rhodecode/templates/admin/repos/repo_add_base.mako:90
7036 7079 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:179
7037 7080 #: rhodecode/templates/admin/users/user_edit_profile.mako:81
7038 7081 #: rhodecode/templates/forks/fork.mako:63
7039 7082 msgid "Plain text format with {metatags} support."
7040 7083 msgstr ""
7041 7084
7042 7085 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:67
7043 7086 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:66
7044 7087 #: rhodecode/templates/admin/repos/repo_add_base.mako:92
7045 7088 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:181
7046 7089 #: rhodecode/templates/admin/users/user_edit_profile.mako:83
7047 7090 #: rhodecode/templates/forks/fork.mako:65
7048 7091 msgid "Plain text format."
7049 7092 msgstr ""
7050 7093
7051 7094 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:79
7052 7095 #: rhodecode/templates/admin/repos/repo_add_base.mako:104
7053 7096 msgid "Copy Parent Group Permissions"
7054 7097 msgstr ""
7055 7098
7056 7099 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:83
7057 7100 #: rhodecode/templates/admin/repos/repo_add_base.mako:108
7058 7101 msgid "Copy permissions from parent repository group."
7059 7102 msgstr ""
7060 7103
7061 7104 #: rhodecode/templates/admin/repo_groups/repo_group_add.mako:88
7062 7105 msgid "Create Repository Group"
7063 7106 msgstr ""
7064 7107
7065 7108 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:5
7066 7109 #, python-format
7067 7110 msgid "%s repository group settings"
7068 7111 msgstr ""
7069 7112
7070 7113 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:31
7071 7114 #: rhodecode/templates/admin/repos/repo_edit.mako:45
7072 7115 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:8
7073 7116 #: rhodecode/templates/admin/users/user_edit_advanced.mako:30
7074 7117 msgid "Access Permissions"
7075 7118 msgstr ""
7076 7119
7077 7120 #: rhodecode/templates/admin/repo_groups/repo_group_edit.mako:32
7078 7121 #: rhodecode/templates/admin/repos/repo_edit.mako:51
7079 7122 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:36
7080 7123 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:24
7081 7124 #: rhodecode/templates/admin/users/user_edit.mako:42
7082 7125 msgid "Advanced"
7083 7126 msgstr ""
7084 7127
7085 7128 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:7
7086 7129 msgid "Repository Group ID"
7087 7130 msgstr ""
7088 7131
7089 7132 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:10
7090 7133 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:8
7091 7134 msgid "Updated on"
7092 7135 msgstr ""
7093 7136
7094 7137 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:11
7095 7138 msgid "Cached Commit source"
7096 7139 msgstr ""
7097 7140
7098 7141 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:12
7099 7142 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:9
7100 7143 msgid "Cached Commit id"
7101 7144 msgstr ""
7102 7145
7103 7146 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:13
7104 7147 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:10
7105 7148 msgid "Cached Commit date"
7106 7149 msgstr ""
7107 7150
7108 7151 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:15
7109 7152 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:11
7110 7153 msgid "Cached Commit data"
7111 7154 msgstr ""
7112 7155
7113 7156 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:17
7114 7157 msgid "Is Personal Group"
7115 7158 msgstr ""
7116 7159
7117 7160 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:19
7118 7161 msgid "Total repositories"
7119 7162 msgstr ""
7120 7163
7121 7164 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:20
7122 7165 msgid "Top level repositories"
7123 7166 msgstr ""
7124 7167
7125 7168 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:22
7126 7169 msgid "Children groups"
7127 7170 msgstr ""
7128 7171
7129 7172 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:28
7130 7173 msgid "Repository Group Advanced: {}"
7131 7174 msgstr ""
7132 7175
7133 7176 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:38
7134 7177 msgid "Delete repository group"
7135 7178 msgstr ""
7136 7179
7137 7180 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:67
7138 7181 #, python-format
7139 7182 msgid "Confirm to delete this group: %s"
7140 7183 msgstr ""
7141 7184
7142 7185 #: rhodecode/templates/admin/repo_groups/repo_group_edit_advanced.mako:68
7143 7186 msgid "Delete this repository group"
7144 7187 msgstr ""
7145 7188
7146 7189 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:5
7147 7190 msgid "Repository Group Permissions: {}"
7148 7191 msgstr ""
7149 7192
7150 7193 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:15
7151 7194 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:15
7152 7195 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:19
7153 7196 msgid "User/User Group"
7154 7197 msgstr ""
7155 7198
7156 7199 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:32
7157 7200 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:31
7158 7201 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:37
7159 7202 msgid "super-admin"
7160 7203 msgstr ""
7161 7204
7162 7205 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:35
7163 7206 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:34
7164 7207 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:40
7165 7208 msgid "owner"
7166 7209 msgstr ""
7167 7210
7168 7211 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:66
7169 7212 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:119
7170 7213 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:92
7171 7214 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:72
7172 7215 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:125
7173 7216 msgid "permission for other logged in and anonymous users"
7174 7217 msgstr ""
7175 7218
7176 7219 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:68
7177 7220 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:121
7178 7221 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:94
7179 7222 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:74
7180 7223 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:127
7181 7224 msgid "permission for other logged in users"
7182 7225 msgstr ""
7183 7226
7184 7227 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:74
7185 7228 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:127
7186 7229 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:100
7187 7230 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:80
7188 7231 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:133
7189 7232 msgid "This entry is a duplicate, most probably left-over from previously set permission. This user has a higher permission set, so this entry is inactive. Please revoke this permission manually."
7190 7233 msgstr ""
7191 7234
7192 7235 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:74
7193 7236 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:127
7194 7237 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:100
7195 7238 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:80
7196 7239 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:133
7197 7240 msgid "inactive duplicate"
7198 7241 msgstr ""
7199 7242
7200 7243 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:87
7201 7244 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:177
7202 7245 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:121
7203 7246 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:170
7204 7247 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:46
7205 7248 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:93
7206 7249 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:182
7207 7250 msgid "Remove"
7208 7251 msgstr ""
7209 7252
7210 7253 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:134
7211 7254 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:140
7212 7255 msgid "delegated admin"
7213 7256 msgstr ""
7214 7257
7215 7258 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:172
7216 7259 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:165
7217 7260 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:177
7218 7261 msgid "members"
7219 7262 msgstr ""
7220 7263
7221 7264 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:204
7222 7265 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:197
7223 7266 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:208
7224 7267 msgid "Add user/user group"
7225 7268 msgstr ""
7226 7269
7227 7270 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:214
7228 7271 msgid "Apply to children"
7229 7272 msgstr ""
7230 7273
7231 7274 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:220
7232 7275 msgid "Both"
7233 7276 msgstr ""
7234 7277
7235 7278 #: rhodecode/templates/admin/repo_groups/repo_group_edit_permissions.mako:221
7236 7279 msgid "Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen."
7237 7280 msgstr ""
7238 7281
7239 7282 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:6
7240 7283 msgid "Repository Group Settings: {}"
7241 7284 msgstr ""
7242 7285
7243 7286 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:31
7244 7287 msgid "Optional select a parent group to move this repository group into."
7245 7288 msgstr ""
7246 7289
7247 7290 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.mako:49
7248 7291 msgid "Change owner of this repository group."
7249 7292 msgstr ""
7250 7293
7251 7294 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:5
7252 7295 msgid "Repository groups administration"
7253 7296 msgstr ""
7254 7297
7255 7298 #: rhodecode/templates/admin/repo_groups/repo_groups.mako:82
7256 7299 msgid "Number of top level repositories"
7257 7300 msgstr ""
7258 7301
7259 7302 #: rhodecode/templates/admin/repos/repo_add.mako:5
7260 7303 msgid "Add repository"
7261 7304 msgstr ""
7262 7305
7263 7306 #: rhodecode/templates/admin/repos/repo_add_base.mako:9
7264 7307 msgid "Repository name"
7265 7308 msgstr ""
7266 7309
7267 7310 #: rhodecode/templates/admin/repos/repo_add_base.mako:14
7268 7311 msgid "Import Existing Repository ?"
7269 7312 msgstr ""
7270 7313
7271 7314 #: rhodecode/templates/admin/repos/repo_add_base.mako:23
7272 7315 #: rhodecode/templates/base/base.mako:332
7273 7316 msgid "Clone from"
7274 7317 msgstr ""
7275 7318
7276 7319 #: rhodecode/templates/admin/repos/repo_add_base.mako:52
7277 7320 #: rhodecode/templates/forks/fork.mako:50
7278 7321 msgid "Optionally select a group to put this repository into."
7279 7322 msgstr ""
7280 7323
7281 7324 #: rhodecode/templates/admin/repos/repo_add_base.mako:78
7282 7325 msgid "Set the type of repository to create."
7283 7326 msgstr ""
7284 7327
7285 7328 #: rhodecode/templates/admin/repos/repo_add_base.mako:94
7286 7329 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:183
7287 7330 #: rhodecode/templates/forks/fork.mako:67
7288 7331 msgid "Add a README file for longer descriptions"
7289 7332 msgstr ""
7290 7333
7291 7334 #: rhodecode/templates/admin/repos/repo_add_base.mako:121
7292 7335 msgid "Create Repository"
7293 7336 msgstr ""
7294 7337
7295 7338 #: rhodecode/templates/admin/repos/repo_creating.mako:5
7296 7339 msgid "{} Creating repository"
7297 7340 msgstr ""
7298 7341
7299 7342 #: rhodecode/templates/admin/repos/repo_creating.mako:12
7300 7343 msgid "Creating repository"
7301 7344 msgstr ""
7302 7345
7303 7346 #: rhodecode/templates/admin/repos/repo_creating.mako:26
7304 7347 #, python-format
7305 7348 msgid "Repository \"%(repo_name)s\" is being created, you will be redirected when this process is finished.repo_name"
7306 7349 msgstr ""
7307 7350
7308 7351 #: rhodecode/templates/admin/repos/repo_edit.mako:8
7309 7352 msgid "{} repository settings"
7310 7353 msgstr ""
7311 7354
7312 7355 #: rhodecode/templates/admin/repos/repo_edit.mako:48
7313 7356 msgid "Branch Permissions"
7314 7357 msgstr ""
7315 7358
7316 7359 #: rhodecode/templates/admin/repos/repo_edit.mako:54
7317 7360 msgid "VCS"
7318 7361 msgstr ""
7319 7362
7320 7363 #: rhodecode/templates/admin/repos/repo_edit.mako:57
7321 7364 msgid "Extra Fields"
7322 7365 msgstr ""
7323 7366
7324 7367 #: rhodecode/templates/admin/repos/repo_edit.mako:60
7325 7368 msgid "Issue Tracker"
7326 7369 msgstr ""
7327 7370
7328 7371 #: rhodecode/templates/admin/repos/repo_edit.mako:63
7329 7372 #: rhodecode/templates/admin/users/user_edit.mako:49
7330 7373 #: rhodecode/templates/admin/users/user_edit_caches.mako:7
7331 7374 msgid "Caches"
7332 7375 msgstr ""
7333 7376
7334 7377 #: rhodecode/templates/admin/repos/repo_edit.mako:67
7335 7378 msgid "Remote sync"
7336 7379 msgstr ""
7337 7380
7338 7381 #: rhodecode/templates/admin/repos/repo_edit.mako:71
7339 7382 msgid "Statistics"
7340 7383 msgstr ""
7341 7384
7342 7385 #: rhodecode/templates/admin/repos/repo_edit.mako:78
7343 7386 msgid "Reviewer Rules"
7344 7387 msgstr ""
7345 7388
7346 7389 #: rhodecode/templates/admin/repos/repo_edit.mako:82
7347 7390 msgid "Automation"
7348 7391 msgstr ""
7349 7392
7350 7393 #: rhodecode/templates/admin/repos/repo_edit.mako:85
7351 7394 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:3
7352 7395 msgid "Maintenance"
7353 7396 msgstr ""
7354 7397
7355 7398 #: rhodecode/templates/admin/repos/repo_edit.mako:88
7356 7399 msgid "Strip"
7357 7400 msgstr ""
7358 7401
7359 7402 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:5
7360 7403 msgid "Repository ID"
7361 7404 msgstr ""
7362 7405
7363 7406 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:12
7364 7407 msgid "Attached scoped tokens"
7365 7408 msgstr ""
7366 7409
7367 7410 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:13
7368 7411 msgid "Pull requests source"
7369 7412 msgstr ""
7370 7413
7371 7414 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:14
7372 7415 msgid "Pull requests target"
7373 7416 msgstr ""
7374 7417
7375 7418 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:15
7376 7419 msgid "Attached Artifacts"
7377 7420 msgstr ""
7378 7421
7379 7422 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:21
7380 7423 #, python-format
7381 7424 msgid "Repository: %s"
7382 7425 msgstr ""
7383 7426
7384 7427 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:31
7385 7428 msgid "Fork Reference"
7386 7429 msgstr ""
7387 7430
7388 7431 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:37
7389 7432 #, python-format
7390 7433 msgid "This repository is a fork of %(repo_link)s"
7391 7434 msgstr ""
7392 7435
7393 7436 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:43
7394 7437 msgid "Set"
7395 7438 msgstr ""
7396 7439
7397 7440 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:46
7398 7441 msgid "Manually set this repository as a fork of another from the list"
7399 7442 msgstr ""
7400 7443
7401 7444 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:55
7402 7445 msgid "Public Journal Visibility"
7403 7446 msgstr ""
7404 7447
7405 7448 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:62
7406 7449 msgid "Remove from Public Journal"
7407 7450 msgstr ""
7408 7451
7409 7452 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:66
7410 7453 msgid "Add to Public Journal"
7411 7454 msgstr ""
7412 7455
7413 7456 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:71
7414 7457 msgid "All actions made on this repository will be visible to everyone following the public journal."
7415 7458 msgstr ""
7416 7459
7417 7460 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:80
7418 7461 msgid "Locking state"
7419 7462 msgstr ""
7420 7463
7421 7464 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:89
7422 7465 msgid "This Repository is not currently locked."
7423 7466 msgstr ""
7424 7467
7425 7468 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:99
7426 7469 msgid "Unlock repository"
7427 7470 msgstr ""
7428 7471
7429 7472 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:107
7430 7473 msgid "Lock repository"
7431 7474 msgstr ""
7432 7475
7433 7476 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:113
7434 7477 msgid "Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again."
7435 7478 msgstr ""
7436 7479
7437 7480 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:123
7438 7481 msgid "Hooks"
7439 7482 msgstr ""
7440 7483
7441 7484 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:127
7442 7485 msgid "Hook type"
7443 7486 msgstr ""
7444 7487
7445 7488 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:128
7446 7489 msgid "Hook version"
7447 7490 msgstr ""
7448 7491
7449 7492 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:129
7450 7493 msgid "Current version"
7451 7494 msgstr ""
7452 7495
7453 7496 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:132
7454 7497 msgid "PRE HOOK"
7455 7498 msgstr ""
7456 7499
7457 7500 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:137
7458 7501 msgid "POST HOOK"
7459 7502 msgstr ""
7460 7503
7461 7504 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:143
7462 7505 msgid "Unable to read hook information from VCS Server"
7463 7506 msgstr ""
7464 7507
7465 7508 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:149
7466 7509 msgid "Confirm to reinstall hooks for this repository."
7467 7510 msgstr ""
7468 7511
7469 7512 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:150
7470 7513 msgid "Update Hooks"
7471 7514 msgstr ""
7472 7515
7473 7516 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:154
7474 7517 msgid "Outdated hooks detected, please update hooks using `Update Hooks` action."
7475 7518 msgstr ""
7476 7519
7477 7520 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:162
7478 7521 msgid "Archive repository"
7479 7522 msgstr ""
7480 7523
7481 7524 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:176
7482 7525 msgid "Archive this repository"
7483 7526 msgstr ""
7484 7527
7485 7528 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:183
7486 7529 msgid "Archiving the repository will make it entirely read-only. The repository cannot be committed to.It is hidden from the search results and dashboard. "
7487 7530 msgstr ""
7488 7531
7489 7532 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:195
7490 7533 msgid "Delete repository"
7491 7534 msgstr ""
7492 7535
7493 7536 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:206
7494 7537 msgid "Detach forks"
7495 7538 msgstr ""
7496 7539
7497 7540 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:211
7498 7541 msgid "Delete forks"
7499 7542 msgstr ""
7500 7543
7501 7544 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:221
7502 7545 msgid "Consider to archive this repository instead."
7503 7546 msgstr ""
7504 7547
7505 7548 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:234
7506 7549 msgid "Delete this repository"
7507 7550 msgstr ""
7508 7551
7509 7552 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:239
7510 7553 msgid "This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools."
7511 7554 msgstr ""
7512 7555
7513 7556 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:273
7514 7557 msgid "Change repository"
7515 7558 msgstr ""
7516 7559
7517 7560 #: rhodecode/templates/admin/repos/repo_edit_advanced.mako:273
7518 7561 msgid "Pick repository"
7519 7562 msgstr ""
7520 7563
7521 7564 #: rhodecode/templates/admin/repos/repo_edit_audit.mako:7
7522 7565 msgid "Repository Audit Logs"
7523 7566 msgstr ""
7524 7567
7525 7568 #: rhodecode/templates/admin/repos/repo_edit_audit.mako:14
7526 7569 #: rhodecode/templates/admin/users/user_edit_audit.mako:17
7527 7570 msgid "audit filter..."
7528 7571 msgstr ""
7529 7572
7530 7573 #: rhodecode/templates/admin/repos/repo_edit_automation.mako:3
7531 7574 msgid "Repo Automation"
7532 7575 msgstr ""
7533 7576
7534 7577 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:3
7535 7578 msgid "Invalidate Cache for Repository"
7536 7579 msgstr ""
7537 7580
7538 7581 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:7
7539 7582 msgid "Manually invalidate the repository cache. On the next access a repository cache will be recreated."
7540 7583 msgstr ""
7541 7584
7542 7585 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:10
7543 7586 msgid "Cache purge can be automated by such api call. Can be called periodically in crontab etc."
7544 7587 msgstr ""
7545 7588
7546 7589 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:23
7547 7590 msgid "Invalidate repository cache"
7548 7591 msgstr ""
7549 7592
7550 7593 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:36
7551 7594 msgid "Invalidation keys"
7552 7595 msgstr ""
7553 7596
7554 7597 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:44
7555 7598 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:88
7556 7599 #: rhodecode/templates/admin/users/user_edit_caches.mako:22
7557 7600 msgid "Show all"
7558 7601 msgstr ""
7559 7602
7560 7603 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:48
7561 7604 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:92
7562 7605 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:11
7563 7606 msgid "Key"
7564 7607 msgstr ""
7565 7608
7566 7609 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:49
7567 7610 msgid "State UID"
7568 7611 msgstr ""
7569 7612
7570 7613 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:50
7571 7614 msgid "Namespace"
7572 7615 msgstr ""
7573 7616
7574 7617 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:69
7575 7618 msgid "Cache keys"
7576 7619 msgstr ""
7577 7620
7578 7621 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:93
7579 7622 msgid "Region"
7580 7623 msgstr ""
7581 7624
7582 7625 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:110
7583 7626 msgid "Shadow Repositories"
7584 7627 msgstr ""
7585 7628
7586 7629 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:122
7587 7630 msgid "No Shadow repositories exist for this repository."
7588 7631 msgstr ""
7589 7632
7590 7633 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:133
7591 7634 msgid "Diff Caches"
7592 7635 msgstr ""
7593 7636
7594 7637 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:141
7595 7638 msgid "Cached diff name"
7596 7639 msgstr ""
7597 7640
7598 7641 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:149
7599 7642 msgid "Cached diff files"
7600 7643 msgstr ""
7601 7644
7602 7645 #: rhodecode/templates/admin/repos/repo_edit_caches.mako:153
7603 7646 msgid "Cached diff size"
7604 7647 msgstr ""
7605 7648
7606 7649 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:3
7607 7650 msgid "Custom extra fields for this repository"
7608 7651 msgstr ""
7609 7652
7610 7653 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:10
7611 7654 msgid "Label"
7612 7655 msgstr ""
7613 7656
7614 7657 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:24
7615 7658 #, python-format
7616 7659 msgid "Confirm to delete this field: %s"
7617 7660 msgstr ""
7618 7661
7619 7662 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:40
7620 7663 msgid "New Field Key"
7621 7664 msgstr ""
7622 7665
7623 7666 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:48
7624 7667 msgid "New Field Label"
7625 7668 msgstr ""
7626 7669
7627 7670 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:51
7628 7671 msgid "Enter short label"
7629 7672 msgstr ""
7630 7673
7631 7674 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:57
7632 7675 msgid "New Field Description"
7633 7676 msgstr ""
7634 7677
7635 7678 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:60
7636 7679 msgid "Enter a full description for the field"
7637 7680 msgstr ""
7638 7681
7639 7682 #: rhodecode/templates/admin/repos/repo_edit_fields.mako:73
7640 7683 msgid "Extra fields are disabled. You can enable them from the Admin/Settings/Visual page."
7641 7684 msgstr ""
7642 7685
7643 7686 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:9
7644 7687 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:9
7645 7688 msgid "Inherit from global settings"
7646 7689 msgstr ""
7647 7690
7648 7691 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:14
7649 7692 msgid "Select to inherit global patterns for issue tracker."
7650 7693 msgstr ""
7651 7694
7652 7695 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:24
7653 7696 msgid "Inherited Issue Tracker Patterns"
7654 7697 msgstr ""
7655 7698
7656 7699 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:31
7657 7700 #: rhodecode/templates/base/issue_tracker_settings.mako:80
7658 7701 #: rhodecode/templates/base/perms_summary.mako:174
7659 7702 msgid "Pattern"
7660 7703 msgstr ""
7661 7704
7662 7705 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:32
7663 7706 #: rhodecode/templates/base/issue_tracker_settings.mako:81
7664 7707 msgid "Url"
7665 7708 msgstr ""
7666 7709
7667 7710 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:33
7668 7711 msgid "Prefix"
7669 7712 msgstr ""
7670 7713
7671 7714 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:75
7672 7715 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:5
7673 7716 msgid "Issue Tracker / Wiki Patterns"
7674 7717 msgstr ""
7675 7718
7676 7719 #: rhodecode/templates/admin/repos/repo_edit_issuetracker.mako:95
7677 7720 #: rhodecode/templates/admin/settings/settings_issuetracker.mako:24
7678 7721 msgid "Test Patterns"
7679 7722 msgstr ""
7680 7723
7681 7724 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:8
7682 7725 msgid "Perform maintenance tasks for this repo"
7683 7726 msgstr ""
7684 7727
7685 7728 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:10
7686 7729 msgid "Following tasks will be performed"
7687 7730 msgstr ""
7688 7731
7689 7732 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:17
7690 7733 msgid "Maintenance can be automated by such api call. Can be called periodically in crontab etc."
7691 7734 msgstr ""
7692 7735
7693 7736 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:25
7694 7737 msgid "No maintenance tasks for this repo available"
7695 7738 msgstr ""
7696 7739
7697 7740 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:34
7698 7741 msgid "Run Maintenance"
7699 7742 msgstr ""
7700 7743
7701 7744 #: rhodecode/templates/admin/repos/repo_edit_maintenance.mako:49
7702 7745 msgid "Performing Maintenance"
7703 7746 msgstr ""
7704 7747
7705 7748 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:5
7706 7749 msgid "Repository Access Permissions"
7707 7750 msgstr ""
7708 7751
7709 7752 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:55
7710 7753 msgid "private repository"
7711 7754 msgstr ""
7712 7755
7713 7756 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:60
7714 7757 msgid "only users/user groups explicitly added here will have access"
7715 7758 msgstr ""
7716 7759
7717 7760 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:62
7718 7761 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:124
7719 7762 msgid "Private repositories are only visible to people explicitly added as collaborators. Default permissions wont apply"
7720 7763 msgstr ""
7721 7764
7722 7765 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:63
7723 7766 msgid "un-set private mode"
7724 7767 msgstr ""
7725 7768
7726 7769 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:109
7727 7770 msgid "used by {} branch rule, requires write or higher permissions"
7728 7771 msgstr ""
7729 7772
7730 7773 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:111
7731 7774 msgid "used by {} branch rules, requires write or higher permissions"
7732 7775 msgstr ""
7733 7776
7734 7777 #: rhodecode/templates/admin/repos/repo_edit_permissions.mako:125
7735 7778 msgid "set private mode"
7736 7779 msgstr ""
7737 7780
7738 7781 #: rhodecode/templates/admin/repos/repo_edit_permissions_branch.mako:3
7739 7782 msgid "Repository Branch Permissions."
7740 7783 msgstr ""
7741 7784
7742 7785 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:3
7743 7786 msgid "Remote Sync"
7744 7787 msgstr ""
7745 7788
7746 7789 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:7
7747 7790 msgid "Manually pull/push changes from/to external URLs."
7748 7791 msgstr ""
7749 7792
7750 7793 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:11
7751 7794 msgid "Pull url"
7752 7795 msgstr ""
7753 7796
7754 7797 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:16
7755 7798 msgid "This repository does not have any pull url set."
7756 7799 msgstr ""
7757 7800
7758 7801 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:17
7759 7802 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:60
7760 7803 msgid "Set remote url."
7761 7804 msgstr ""
7762 7805
7763 7806 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:26
7764 7807 msgid "Pull can be automated by such api call. Can be called periodically in crontab etc."
7765 7808 msgstr ""
7766 7809
7767 7810 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:44
7768 7811 msgid "Pull changes from remote location"
7769 7812 msgstr ""
7770 7813
7771 7814 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:54
7772 7815 msgid "Push url"
7773 7816 msgstr ""
7774 7817
7775 7818 #: rhodecode/templates/admin/repos/repo_edit_remote.mako:59
7776 7819 msgid "This repository does not have any push url set."
7777 7820 msgstr ""
7778 7821
7779 7822 #: rhodecode/templates/admin/repos/repo_edit_reviewers.mako:3
7780 7823 msgid "Default Reviewer Rules"
7781 7824 msgstr ""
7782 7825
7783 7826 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:6
7784 7827 #, python-format
7785 7828 msgid "Settings for Repository: %s"
7786 7829 msgstr ""
7787 7830
7788 7831 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:22
7789 7832 msgid "permalink id"
7790 7833 msgstr ""
7791 7834
7792 7835 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:22
7793 7836 msgid "what is that ?"
7794 7837 msgstr ""
7795 7838
7796 7839 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:24
7797 7840 msgid "URL by id"
7798 7841 msgstr ""
7799 7842
7800 7843 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:25
7801 7844 msgid ""
7802 7845 "In case this repository is renamed or moved into another group the repository url changes.\n"
7803 7846 " Using above url guarantees that this repository will always be accessible under such url.\n"
7804 7847 " Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service."
7805 7848 msgstr ""
7806 7849
7807 7850 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:41
7808 7851 #, python-format
7809 7852 msgid "Select my personal group (`%(repo_group_name)s`)"
7810 7853 msgstr ""
7811 7854
7812 7855 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:44
7813 7856 msgid "Optional select a group to put this repository into."
7814 7857 msgstr ""
7815 7858
7816 7859 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:52
7817 7860 msgid "Remote pull uri"
7818 7861 msgstr ""
7819 7862
7820 7863 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:60
7821 7864 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:102
7822 7865 #: rhodecode/templates/base/perms_summary.mako:116
7823 7866 #: rhodecode/templates/base/perms_summary.mako:233
7824 7867 #: rhodecode/templates/base/perms_summary.mako:311
7825 7868 #: rhodecode/templates/base/perms_summary.mako:313
7826 7869 #: rhodecode/templates/debug_style/form-elements.html:45
7827 7870 msgid "edit"
7828 7871 msgstr ""
7829 7872
7830 7873 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:66
7831 7874 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:108
7832 7875 msgid "enter new value, or leave empty to remove"
7833 7876 msgstr ""
7834 7877
7835 7878 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:76
7836 7879 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:118
7837 7880 msgid "cancel"
7838 7881 msgstr ""
7839 7882
7840 7883 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:87
7841 7884 msgid "http[s] url where from repository was imported. This field can used for doing {sync_link}."
7842 7885 msgstr ""
7843 7886
7844 7887 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:88
7845 7888 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:130
7846 7889 msgid "This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display."
7847 7890 msgstr ""
7848 7891
7849 7892 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:94
7850 7893 msgid "Remote push uri"
7851 7894 msgstr ""
7852 7895
7853 7896 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:129
7854 7897 msgid "http[s] url to sync data back. This field can used for doing {sync_link}."
7855 7898 msgstr ""
7856 7899
7857 7900 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:141
7858 7901 msgid "Landing commit"
7859 7902 msgstr ""
7860 7903
7861 7904 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:146
7862 7905 msgid "The default commit for file pages, downloads, full text search index, and README generation."
7863 7906 msgstr ""
7864 7907
7865 7908 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:164
7866 7909 msgid "Change owner of this repository."
7867 7910 msgstr ""
7868 7911
7869 7912 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:194
7870 7913 #: rhodecode/templates/data_table/_dt_elements.mako:104
7871 7914 msgid "Private repository"
7872 7915 msgstr ""
7873 7916
7874 7917 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:204
7875 7918 #: rhodecode/templates/summary/components.mako:262
7876 7919 msgid "Enable statistics"
7877 7920 msgstr ""
7878 7921
7879 7922 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:209
7880 7923 msgid "Enable statistics window on summary page."
7881 7924 msgstr ""
7882 7925
7883 7926 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:214
7884 7927 #: rhodecode/templates/summary/components.mako:184
7885 7928 msgid "Enable downloads"
7886 7929 msgstr ""
7887 7930
7888 7931 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:219
7889 7932 msgid "Enable download menu on summary page."
7890 7933 msgstr ""
7891 7934
7892 7935 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:224
7893 7936 msgid "Enable automatic locking"
7894 7937 msgstr ""
7895 7938
7896 7939 #: rhodecode/templates/admin/repos/repo_edit_settings.mako:229
7897 7940 msgid "Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user"
7898 7941 msgstr ""
7899 7942
7900 7943 #: rhodecode/templates/admin/repos/repo_edit_statistics.mako:3
7901 7944 msgid "Repository statistics"
7902 7945 msgstr ""
7903 7946
7904 7947 #: rhodecode/templates/admin/repos/repo_edit_statistics.mako:11
7905 7948 msgid "Processed commits"
7906 7949 msgstr ""
7907 7950
7908 7951 #: rhodecode/templates/admin/repos/repo_edit_statistics.mako:12
7909 7952 msgid "Processed progress"
7910 7953 msgstr ""
7911 7954
7912 7955 #: rhodecode/templates/admin/repos/repo_edit_statistics.mako:15
7913 7956 msgid "Reset statistics"
7914 7957 msgstr ""
7915 7958
7916 7959 #: rhodecode/templates/admin/repos/repo_edit_statistics.mako:15
7917 7960 msgid "Confirm to remove current statistics"
7918 7961 msgstr ""
7919 7962
7920 7963 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:3
7921 7964 msgid "Strip commits from repository"
7922 7965 msgstr ""
7923 7966
7924 7967 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:7
7925 7968 #, python-format
7926 7969 msgid "Please provide up to %d commits commits to strip"
7927 7970 msgstr ""
7928 7971
7929 7972 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:9
7930 7973 msgid "In the first step commits will be verified for existance in the repository"
7931 7974 msgstr ""
7932 7975
7933 7976 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:10
7934 7977 msgid "In the second step, correct commits will be available for stripping"
7935 7978 msgstr ""
7936 7979
7937 7980 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:16
7938 7981 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:52
7939 7982 msgid "Enter full 40 character commit sha"
7940 7983 msgstr ""
7941 7984
7942 7985 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:18
7943 7986 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:54
7944 7987 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:95
7945 7988 msgid "Add another commit"
7946 7989 msgstr ""
7947 7990
7948 7991 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:27
7949 7992 msgid "Check commits"
7950 7993 msgstr ""
7951 7994
7952 7995 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:33
7953 7996 msgid "Sorry this functionality is not available for SVN repository"
7954 7997 msgstr ""
7955 7998
7956 7999 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:114
7957 8000 msgid "Checking commits"
7958 8001 msgstr ""
7959 8002
7960 8003 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:142
7961 8004 msgid " commit verified positive"
7962 8005 msgstr ""
7963 8006
7964 8007 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:154
7965 8008 msgid " commit verified negative"
7966 8009 msgstr ""
7967 8010
7968 8011 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:179
7969 8012 msgid " commit striped successfully"
7970 8013 msgstr ""
7971 8014
7972 8015 #: rhodecode/templates/admin/repos/repo_edit_strip.mako:182
7973 8016 msgid " commit strip failed"
7974 8017 msgstr ""
7975 8018
7976 8019 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:13
7977 8020 msgid "Select to inherit global vcs settings."
7978 8021 msgstr ""
7979 8022
7980 8023 #: rhodecode/templates/admin/repos/repo_edit_vcs.mako:43
7981 8024 #: rhodecode/templates/admin/settings/settings_global.mako:140
7982 8025 #: rhodecode/templates/admin/settings/settings_labs.mako:48
7983 8026 #: rhodecode/templates/admin/settings/settings_vcs.mako:13
7984 8027 #: rhodecode/templates/admin/settings/settings_visual.mako:214
7985 8028 msgid "Save settings"
7986 8029 msgstr ""
7987 8030
7988 8031 #: rhodecode/templates/admin/repos/repos.mako:5
7989 8032 msgid "Repositories administration"
7990 8033 msgstr ""
7991 8034
7992 8035 #: rhodecode/templates/admin/repos/repos.mako:110
7993 8036 msgid "State"
7994 8037 msgstr ""
7995 8038
7996 8039 #: rhodecode/templates/admin/settings/settings_automation.mako:3
7997 8040 msgid "Admin Automation"
7998 8041 msgstr ""
7999 8042
8000 8043 #: rhodecode/templates/admin/settings/settings_email.mako:3
8001 8044 msgid "Email Configuration"
8002 8045 msgstr ""
8003 8046
8004 8047 #: rhodecode/templates/admin/settings/settings_email.mako:8
8005 8048 msgid "Email prefix"
8006 8049 msgstr ""
8007 8050
8008 8051 #: rhodecode/templates/admin/settings/settings_email.mako:9
8009 8052 msgid "Email from"
8010 8053 msgstr ""
8011 8054
8012 8055 #: rhodecode/templates/admin/settings/settings_email.mako:11
8013 8056 msgid "SMTP server"
8014 8057 msgstr ""
8015 8058
8016 8059 #: rhodecode/templates/admin/settings/settings_email.mako:12
8017 8060 msgid "SMTP username"
8018 8061 msgstr ""
8019 8062
8020 8063 #: rhodecode/templates/admin/settings/settings_email.mako:13
8021 8064 msgid "SMTP password"
8022 8065 msgstr ""
8023 8066
8024 8067 #: rhodecode/templates/admin/settings/settings_email.mako:14
8025 8068 msgid "SMTP port"
8026 8069 msgstr ""
8027 8070
8028 8071 #: rhodecode/templates/admin/settings/settings_email.mako:16
8029 8072 msgid "SMTP use TLS"
8030 8073 msgstr ""
8031 8074
8032 8075 #: rhodecode/templates/admin/settings/settings_email.mako:17
8033 8076 msgid "SMTP use SSL"
8034 8077 msgstr ""
8035 8078
8036 8079 #: rhodecode/templates/admin/settings/settings_email.mako:28
8037 8080 msgid "You can adjust those settings in [DEFAULT] section of .ini file located at"
8038 8081 msgstr ""
8039 8082
8040 8083 #: rhodecode/templates/admin/settings/settings_email.mako:36
8041 8084 msgid "Test Email"
8042 8085 msgstr ""
8043 8086
8044 8087 #: rhodecode/templates/admin/settings/settings_email.mako:42
8045 8088 msgid "enter valid email"
8046 8089 msgstr ""
8047 8090
8048 8091 #: rhodecode/templates/admin/settings/settings_email.mako:46
8049 8092 msgid "Send an auto-generated email from this server to above email..."
8050 8093 msgstr ""
8051 8094
8052 8095 #: rhodecode/templates/admin/settings/settings_exceptions.mako:3
8053 8096 msgid "Exceptions Tracker - Exception ID"
8054 8097 msgstr ""
8055 8098
8056 8099 #: rhodecode/templates/admin/settings/settings_exceptions.mako:8
8057 8100 #: rhodecode/templates/email_templates/exception_tracker.mako:14
8058 8101 msgid "Exception `{}` generated on UTC date: {}"
8059 8102 msgstr ""
8060 8103
8061 8104 #: rhodecode/templates/admin/settings/settings_exceptions.mako:21
8062 8105 msgid "Unable to Read Exception. It might be removed or non-existing."
8063 8106 msgstr ""
8064 8107
8065 8108 #: rhodecode/templates/admin/settings/settings_exceptions.mako:30
8066 8109 msgid "Delete this Exception"
8067 8110 msgstr ""
8068 8111
8069 8112 #: rhodecode/templates/admin/settings/settings_exceptions.mako:40
8070 8113 msgid "Delete This Exception"
8071 8114 msgstr ""
8072 8115
8073 8116 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:3
8074 8117 msgid "Exceptions Tracker "
8075 8118 msgstr ""
8076 8119
8077 8120 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:7
8078 8121 msgid "There is {} stored exception."
8079 8122 msgstr ""
8080 8123
8081 8124 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:9
8082 8125 msgid "There are total {} stored exceptions."
8083 8126 msgstr ""
8084 8127
8085 8128 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:12
8086 8129 msgid "Store directory"
8087 8130 msgstr ""
8088 8131
8089 8132 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:23
8090 8133 msgid "Delete All `{}`"
8091 8134 msgstr ""
8092 8135
8093 8136 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:25
8094 8137 msgid "Delete All"
8095 8138 msgstr ""
8096 8139
8097 8140 #: rhodecode/templates/admin/settings/settings_exceptions_browse.mako:39
8098 8141 msgid "Exceptions Tracker - Showing the last {} Exceptions"
8099 8142 msgstr ""
8100 8143
8101 8144 #: rhodecode/templates/admin/settings/settings_global.mako:5
8102 8145 msgid "Branding"
8103 8146 msgstr ""
8104 8147
8105 8148 #: rhodecode/templates/admin/settings/settings_global.mako:16
8106 8149 msgid "Set a custom title for your RhodeCode instance (limited to 40 characters)."
8107 8150 msgstr ""
8108 8151
8109 8152 #: rhodecode/templates/admin/settings/settings_global.mako:20
8110 8153 msgid "HTTP[S] authentication realm"
8111 8154 msgstr ""
8112 8155
8113 8156 #: rhodecode/templates/admin/settings/settings_global.mako:27
8114 8157 msgid "Set a custom text that is shown as authentication message to clients trying to connect."
8115 8158 msgstr ""
8116 8159
8117 8160 #: rhodecode/templates/admin/settings/settings_global.mako:36
8118 8161 #: rhodecode/templates/admin/users/user_edit_advanced.mako:83
8119 8162 msgid "Personal Repository Group"
8120 8163 msgstr ""
8121 8164
8122 8165 #: rhodecode/templates/admin/settings/settings_global.mako:41
8123 8166 msgid "Create Personal Repository Group"
8124 8167 msgstr ""
8125 8168
8126 8169 #: rhodecode/templates/admin/settings/settings_global.mako:44
8127 8170 msgid "Always create Personal Repository Groups for new users."
8128 8171 msgstr ""
8129 8172
8130 8173 #: rhodecode/templates/admin/settings/settings_global.mako:45
8131 8174 msgid "When creating new users from add user form or API you can still turn this off via a checkbox or flag"
8132 8175 msgstr ""
8133 8176
8134 8177 #: rhodecode/templates/admin/settings/settings_global.mako:49
8135 8178 msgid "Personal Repo Group Pattern"
8136 8179 msgstr ""
8137 8180
8138 8181 #: rhodecode/templates/admin/settings/settings_global.mako:55
8139 8182 msgid "Pattern used to create Personal Repository Groups. Prefix can be other existing repository group path[s], eg. /u/${username}"
8140 8183 msgstr ""
8141 8184
8142 8185 #: rhodecode/templates/admin/settings/settings_global.mako:56
8143 8186 msgid "Available variables are currently ${username} and ${user_id}"
8144 8187 msgstr ""
8145 8188
8146 8189 #: rhodecode/templates/admin/settings/settings_global.mako:64
8147 8190 msgid "Registration Captcha"
8148 8191 msgstr ""
8149 8192
8150 8193 #: rhodecode/templates/admin/settings/settings_global.mako:68
8151 8194 msgid "Google reCaptcha v2 site key."
8152 8195 msgstr ""
8153 8196
8154 8197 #: rhodecode/templates/admin/settings/settings_global.mako:75
8155 8198 msgid "Site key for reCaptcha v2 system."
8156 8199 msgstr ""
8157 8200
8158 8201 #: rhodecode/templates/admin/settings/settings_global.mako:80
8159 8202 msgid "Google reCaptcha v2 secret key."
8160 8203 msgstr ""
8161 8204
8162 8205 #: rhodecode/templates/admin/settings/settings_global.mako:87
8163 8206 msgid "Secret key for reCaptcha v2 system. Setting this value will enable captcha on registration and password reset forms."
8164 8207 msgstr ""
8165 8208
8166 8209 #: rhodecode/templates/admin/settings/settings_global.mako:95
8167 8210 msgid "Custom Header Code"
8168 8211 msgstr ""
8169 8212
8170 8213 #: rhodecode/templates/admin/settings/settings_global.mako:100
8171 8214 #: rhodecode/templates/admin/settings/settings_global.mako:124
8172 8215 #: rhodecode/templates/debug_style/form-elements-small.html:59
8173 8216 #: rhodecode/templates/debug_style/form-elements.html:57
8174 8217 #: rhodecode/templates/debug_style/form-elements.html:82
8175 8218 #: rhodecode/templates/debug_style/form-elements.html:225
8176 8219 #: rhodecode/templates/debug_style/form-elements.html:381
8177 8220 #: rhodecode/templates/debug_style/form-elements.html:407
8178 8221 #: rhodecode/templates/debug_style/form-elements.html:515
8179 8222 #: rhodecode/templates/debug_style/form-elements.html:519
8180 8223 #: rhodecode/templates/debug_style/form-elements.html:537
8181 8224 #: rhodecode/templates/debug_style/form-elements.html:581
8182 8225 #: rhodecode/templates/debug_style/form-inline.html:38
8183 8226 #: rhodecode/templates/debug_style/form-inline.html:139
8184 8227 #: rhodecode/templates/debug_style/form-inline.html:147
8185 8228 #: rhodecode/templates/debug_style/form-vertical.html:60
8186 8229 #: rhodecode/templates/debug_style/forms.html:37
8187 8230 #: rhodecode/templates/debug_style/forms.html:60
8188 8231 #: rhodecode/templates/debug_style/forms.html:78
8189 8232 #: rhodecode/templates/debug_style/forms.html:96
8190 8233 #: rhodecode/templates/debug_style/layout-form-sidebar.html:44
8191 8234 msgid "Templates..."
8192 8235 msgstr ""
8193 8236
8194 8237 #: rhodecode/templates/admin/settings/settings_global.mako:103
8195 8238 #: rhodecode/templates/admin/settings/settings_global.mako:127
8196 8239 #: rhodecode/templates/debug_style/form-elements-small.html:62
8197 8240 #: rhodecode/templates/debug_style/form-elements.html:60
8198 8241 #: rhodecode/templates/debug_style/form-elements.html:85
8199 8242 #: rhodecode/templates/debug_style/form-elements.html:228
8200 8243 #: rhodecode/templates/debug_style/form-elements.html:384
8201 8244 #: rhodecode/templates/debug_style/form-elements.html:410
8202 8245 #: rhodecode/templates/debug_style/form-elements.html:518
8203 8246 #: rhodecode/templates/debug_style/form-elements.html:522
8204 8247 #: rhodecode/templates/debug_style/form-elements.html:540
8205 8248 #: rhodecode/templates/debug_style/form-elements.html:584
8206 8249 #: rhodecode/templates/debug_style/form-inline.html:41
8207 8250 #: rhodecode/templates/debug_style/form-inline.html:142
8208 8251 #: rhodecode/templates/debug_style/form-inline.html:150
8209 8252 #: rhodecode/templates/debug_style/form-vertical.html:63
8210 8253 #: rhodecode/templates/debug_style/forms.html:40
8211 8254 #: rhodecode/templates/debug_style/forms.html:63
8212 8255 #: rhodecode/templates/debug_style/forms.html:81
8213 8256 #: rhodecode/templates/debug_style/forms.html:99
8214 8257 #: rhodecode/templates/debug_style/layout-form-sidebar.html:47
8215 8258 msgid "Server Announcement"
8216 8259 msgstr ""
8217 8260
8218 8261 #: rhodecode/templates/admin/settings/settings_global.mako:104
8219 8262 msgid "Flash message filtering"
8220 8263 msgstr ""
8221 8264
8222 8265 #: rhodecode/templates/admin/settings/settings_global.mako:105
8223 8266 msgid "Custom logos"
8224 8267 msgstr ""
8225 8268
8226 8269 #: rhodecode/templates/admin/settings/settings_global.mako:111
8227 8270 msgid "Custom js/css code added at the end of the <head/> tag."
8228 8271 msgstr ""
8229 8272
8230 8273 #: rhodecode/templates/admin/settings/settings_global.mako:112
8231 8274 msgid "Use <script/> or <style/> tags to define custom scripting or styling."
8232 8275 msgstr ""
8233 8276
8234 8277 #: rhodecode/templates/admin/settings/settings_global.mako:119
8235 8278 msgid "Custom Footer Code"
8236 8279 msgstr ""
8237 8280
8238 8281 #: rhodecode/templates/admin/settings/settings_global.mako:133
8239 8282 msgid "Custom js/css code added at the end of the <body> tag."
8240 8283 msgstr ""
8241 8284
8242 8285 #: rhodecode/templates/admin/settings/settings_global.mako:134
8243 8286 msgid "Use <script> or <style> tags to define custom scripting or styling."
8244 8287 msgstr ""
8245 8288
8246 8289 #: rhodecode/templates/admin/settings/settings_hooks.mako:3
8247 8290 msgid "Built in Mercurial hooks - read only"
8248 8291 msgstr ""
8249 8292
8250 8293 #: rhodecode/templates/admin/settings/settings_hooks.mako:19
8251 8294 msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications."
8252 8295 msgstr ""
8253 8296
8254 8297 #: rhodecode/templates/admin/settings/settings_hooks.mako:27
8255 8298 msgid "Custom hooks"
8256 8299 msgstr ""
8257 8300
8258 8301 #: rhodecode/templates/admin/settings/settings_labs.mako:3
8259 8302 msgid "Labs Settings"
8260 8303 msgstr ""
8261 8304
8262 8305 #: rhodecode/templates/admin/settings/settings_labs.mako:10
8263 8306 msgid "There are no Labs settings currently"
8264 8307 msgstr ""
8265 8308
8266 8309 #: rhodecode/templates/admin/settings/settings_mapping.mako:5
8267 8310 msgid "Import New Groups or Repositories"
8268 8311 msgstr ""
8269 8312
8270 8313 #: rhodecode/templates/admin/settings/settings_mapping.mako:10
8271 8314 msgid "Destroy old data"
8272 8315 msgstr ""
8273 8316
8274 8317 #: rhodecode/templates/admin/settings/settings_mapping.mako:12
8275 8318 msgid "In case a repository or a group was deleted from the filesystem and it still exists in the database, check this option to remove obsolete data from the database."
8276 8319 msgstr ""
8277 8320
8278 8321 #: rhodecode/templates/admin/settings/settings_mapping.mako:16
8279 8322 msgid "Invalidate cache for all repositories"
8280 8323 msgstr ""
8281 8324
8282 8325 #: rhodecode/templates/admin/settings/settings_mapping.mako:18
8283 8326 msgid "Each cache data for repositories will be cleaned with this option selected. Use this to reload data and clear cache keys."
8284 8327 msgstr ""
8285 8328
8286 8329 #: rhodecode/templates/admin/settings/settings_mapping.mako:21
8287 8330 msgid "Rescan Filesystem"
8288 8331 msgstr ""
8289 8332
8290 8333 #: rhodecode/templates/admin/settings/settings_open_source.mako:11
8291 8334 msgid "Licenses of Third Party Packages"
8292 8335 msgstr ""
8293 8336
8294 8337 #: rhodecode/templates/admin/settings/settings_process_management.mako:3
8295 8338 #: rhodecode/templates/admin/settings/settings_system.mako:3
8296 8339 msgid "Checking for updates..."
8297 8340 msgstr ""
8298 8341
8299 8342 #: rhodecode/templates/admin/settings/settings_process_management.mako:9
8300 8343 msgid "Gunicorn process management"
8301 8344 msgstr ""
8302 8345
8303 8346 #: rhodecode/templates/admin/settings/settings_process_management.mako:11
8304 8347 msgid "start auto refresh"
8305 8348 msgstr ""
8306 8349
8307 8350 #: rhodecode/templates/admin/settings/settings_process_management.mako:12
8308 8351 msgid "stop auto refresh"
8309 8352 msgstr ""
8310 8353
8311 8354 #: rhodecode/templates/admin/settings/settings_search.mako:3
8312 8355 msgid "RhodeCode Full Text Search"
8313 8356 msgstr ""
8314 8357
8315 8358 #: rhodecode/templates/admin/settings/settings_sessions.mako:3
8316 8359 msgid "User Sessions Configuration"
8317 8360 msgstr ""
8318 8361
8319 8362 #: rhodecode/templates/admin/settings/settings_sessions.mako:8
8320 8363 msgid "Session type"
8321 8364 msgstr ""
8322 8365
8323 8366 #: rhodecode/templates/admin/settings/settings_sessions.mako:9
8324 8367 msgid "Session expiration period"
8325 8368 msgstr ""
8326 8369
8327 8370 #: rhodecode/templates/admin/settings/settings_sessions.mako:11
8328 8371 msgid "Total sessions"
8329 8372 msgstr ""
8330 8373
8331 8374 #: rhodecode/templates/admin/settings/settings_sessions.mako:12
8332 8375 msgid "Expired sessions ({} days)"
8333 8376 msgstr ""
8334 8377
8335 8378 #: rhodecode/templates/admin/settings/settings_sessions.mako:28
8336 8379 msgid "Cleanup Old Sessions"
8337 8380 msgstr ""
8338 8381
8339 8382 #: rhodecode/templates/admin/settings/settings_sessions.mako:34
8340 8383 msgid "Cleanup user sessions that were not active during chosen time frame."
8341 8384 msgstr ""
8342 8385
8343 8386 #: rhodecode/templates/admin/settings/settings_sessions.mako:35
8344 8387 msgid "After performing this action users whose session will be removed will be required to log in again."
8345 8388 msgstr ""
8346 8389
8347 8390 #: rhodecode/templates/admin/settings/settings_sessions.mako:36
8348 8391 msgid "Picking `All` will log-out you, and all users in the system."
8349 8392 msgstr ""
8350 8393
8351 8394 #: rhodecode/templates/admin/settings/settings_sessions.mako:55
8352 8395 msgid "Confirm to cleanup user sessions"
8353 8396 msgstr ""
8354 8397
8355 8398 #: rhodecode/templates/admin/settings/settings_sessions.mako:56
8356 8399 msgid "Cleanup sessions"
8357 8400 msgstr ""
8358 8401
8359 8402 #: rhodecode/templates/admin/settings/settings_system.mako:9
8360 8403 msgid "System Info"
8361 8404 msgstr ""
8362 8405
8363 8406 #: rhodecode/templates/admin/settings/settings_system.mako:11
8364 8407 msgid "create summary snapshot"
8365 8408 msgstr ""
8366 8409
8367 8410 #: rhodecode/templates/admin/settings/settings_system.mako:46
8368 8411 msgid "Python Packages"
8369 8412 msgstr ""
8370 8413
8371 8414 #: rhodecode/templates/admin/settings/settings_visual.mako:5
8372 8415 #: rhodecode/templates/base/vcs_settings.mako:10
8373 8416 msgid "General"
8374 8417 msgstr ""
8375 8418
8376 8419 #: rhodecode/templates/admin/settings/settings_visual.mako:10
8377 8420 msgid "Use repository extra fields"
8378 8421 msgstr ""
8379 8422
8380 8423 #: rhodecode/templates/admin/settings/settings_visual.mako:12
8381 8424 msgid "Allows storing additional customized fields per repository."
8382 8425 msgstr ""
8383 8426
8384 8427 #: rhodecode/templates/admin/settings/settings_visual.mako:17
8385 8428 msgid "Show RhodeCode version"
8386 8429 msgstr ""
8387 8430
8388 8431 #: rhodecode/templates/admin/settings/settings_visual.mako:19
8389 8432 msgid "Shows or hides a version number of RhodeCode displayed in the footer."
8390 8433 msgstr ""
8391 8434
8392 8435 #: rhodecode/templates/admin/settings/settings_visual.mako:26
8393 8436 msgid "Gravatars"
8394 8437 msgstr ""
8395 8438
8396 8439 #: rhodecode/templates/admin/settings/settings_visual.mako:31
8397 8440 msgid "Use Gravatars based avatars"
8398 8441 msgstr ""
8399 8442
8400 8443 #: rhodecode/templates/admin/settings/settings_visual.mako:33
8401 8444 msgid "Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email."
8402 8445 msgstr ""
8403 8446
8404 8447 #: rhodecode/templates/admin/settings/settings_visual.mako:36
8405 8448 msgid "Gravatar URL"
8406 8449 msgstr ""
8407 8450
8408 8451 #: rhodecode/templates/admin/settings/settings_visual.mako:44
8409 8452 msgid ""
8410 8453 "Gravatar url allows you to use other avatar server application.\n"
8411 8454 " Following variables of the URL will be replaced accordingly.\n"
8412 8455 " {scheme} 'http' or 'https' sent from running RhodeCode server,\n"
8413 8456 " {email} user email,\n"
8414 8457 " {md5email} md5 hash of the user email (like at gravatar.com),\n"
8415 8458 " {size} size of the image that is expected from the server application,\n"
8416 8459 " {netloc} network location/server host of running RhodeCode server"
8417 8460 msgstr ""
8418 8461
8419 8462 #: rhodecode/templates/admin/settings/settings_visual.mako:59
8420 8463 msgid "Meta-Tagging"
8421 8464 msgstr ""
8422 8465
8423 8466 #: rhodecode/templates/admin/settings/settings_visual.mako:64
8424 8467 msgid "Stylify recognised meta tags"
8425 8468 msgstr ""
8426 8469
8427 8470 #: rhodecode/templates/admin/settings/settings_visual.mako:66
8428 8471 msgid "Parses meta tags from repository or repository group description fields and turns them into colored tags."
8429 8472 msgstr ""
8430 8473
8431 8474 #: rhodecode/templates/admin/settings/settings_visual.mako:77
8432 8475 msgid "Dashboard Items"
8433 8476 msgstr ""
8434 8477
8435 8478 #: rhodecode/templates/admin/settings/settings_visual.mako:81
8436 8479 msgid "Main page dashboard items"
8437 8480 msgstr ""
8438 8481
8439 8482 #: rhodecode/templates/admin/settings/settings_visual.mako:87
8440 8483 msgid "Number of items displayed in the main page dashboard before pagination is shown."
8441 8484 msgstr ""
8442 8485
8443 8486 #: rhodecode/templates/admin/settings/settings_visual.mako:91
8444 8487 msgid "Admin pages items"
8445 8488 msgstr ""
8446 8489
8447 8490 #: rhodecode/templates/admin/settings/settings_visual.mako:97
8448 8491 msgid "Number of items displayed in the admin pages grids before pagination is shown."
8449 8492 msgstr ""
8450 8493
8451 8494 #: rhodecode/templates/admin/settings/settings_visual.mako:106
8452 8495 msgid "Commit ID Style"
8453 8496 msgstr ""
8454 8497
8455 8498 #: rhodecode/templates/admin/settings/settings_visual.mako:110
8456 8499 msgid "Commit sha length"
8457 8500 msgstr ""
8458 8501
8459 8502 #: rhodecode/templates/admin/settings/settings_visual.mako:117
8460 8503 msgid ""
8461 8504 "Number of chars to show in commit sha displayed in web interface.\n"
8462 8505 " By default it's shown as r123:9043a6a4c226 this value defines the\n"
8463 8506 " length of the sha after the `r123:` part."
8464 8507 msgstr ""
8465 8508
8466 8509 #: rhodecode/templates/admin/settings/settings_visual.mako:125
8467 8510 msgid "Show commit ID numeric reference"
8468 8511 msgstr ""
8469 8512
8470 8513 #: rhodecode/templates/admin/settings/settings_visual.mako:125
8471 8514 msgid "Commit show revision number"
8472 8515 msgstr ""
8473 8516
8474 8517 #: rhodecode/templates/admin/settings/settings_visual.mako:127
8475 8518 msgid ""
8476 8519 "Show revision number in commit sha displayed in web interface.\n"
8477 8520 " By default it's shown as r123:9043a6a4c226 this value defines the\n"
8478 8521 " if the `r123:` part is shown."
8479 8522 msgstr ""
8480 8523
8481 8524 #: rhodecode/templates/admin/settings/settings_visual.mako:136
8482 8525 #: rhodecode/templates/debug_style/index.html:64
8483 8526 msgid "Icons"
8484 8527 msgstr ""
8485 8528
8486 8529 #: rhodecode/templates/admin/settings/settings_visual.mako:141
8487 8530 msgid "Show public repo icon on repositories"
8488 8531 msgstr ""
8489 8532
8490 8533 #: rhodecode/templates/admin/settings/settings_visual.mako:147
8491 8534 msgid "Show private repo icon on repositories"
8492 8535 msgstr ""
8493 8536
8494 8537 #: rhodecode/templates/admin/settings/settings_visual.mako:149
8495 8538 msgid "Show public/private icons next to repositories names."
8496 8539 msgstr ""
8497 8540
8498 8541 #: rhodecode/templates/admin/settings/settings_visual.mako:156
8499 8542 msgid "Markup Renderer"
8500 8543 msgstr ""
8501 8544
8502 8545 #: rhodecode/templates/admin/settings/settings_visual.mako:163
8503 8546 msgid "Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly."
8504 8547 msgstr ""
8505 8548
8506 8549 #: rhodecode/templates/admin/settings/settings_visual.mako:170
8507 8550 msgid "Clone URL templates"
8508 8551 msgstr ""
8509 8552
8510 8553 #: rhodecode/templates/admin/settings/settings_visual.mako:181
8511 8554 msgid ""
8512 8555 "Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:\n"
8513 8556 " {scheme} 'http' or 'https' sent from running RhodeCode server,\n"
8514 8557 " {user} current user username,\n"
8515 8558 " {sys_user} current system user running this process, Useful for ssh,\n"
8516 8559 " {hostname} hostname of this server running RhodeCode,\n"
8517 8560 " {netloc} network location/server host of running RhodeCode server,\n"
8518 8561 " {repo} full repository name,\n"
8519 8562 " {repoid} ID of repository, can be used to contruct clone-by-id"
8520 8563 msgstr ""
8521 8564
8522 8565 #: rhodecode/templates/admin/settings/settings_visual.mako:196
8523 8566 msgid "Custom Support Link"
8524 8567 msgstr ""
8525 8568
8526 8569 #: rhodecode/templates/admin/settings/settings_visual.mako:204
8527 8570 #, python-format
8528 8571 msgid ""
8529 8572 "Custom url for the support link located at the bottom.\n"
8530 8573 " The default is set to %(default_url)s. In case there's a need\n"
8531 8574 " to change the support link to internal issue tracker, it should be done here.\n"
8532 8575 " "
8533 8576 msgstr ""
8534 8577
8535 8578 #: rhodecode/templates/admin/user_groups/user_group_add.mako:5
8536 8579 msgid "Add user group"
8537 8580 msgstr ""
8538 8581
8539 8582 #: rhodecode/templates/admin/user_groups/user_group_add.mako:13
8540 8583 #: rhodecode/templates/admin/users/user_edit_advanced.mako:14
8541 8584 #: rhodecode/templates/base/base.mako:117
8542 8585 #: rhodecode/templates/base/base.mako:139
8543 8586 msgid "User groups"
8544 8587 msgstr ""
8545 8588
8546 8589 #: rhodecode/templates/admin/user_groups/user_group_add.mako:46
8547 8590 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:50
8548 8591 msgid "Short, optional description for this user group."
8549 8592 msgstr ""
8550 8593
8551 8594 #: rhodecode/templates/admin/user_groups/user_group_add.mako:59
8552 8595 msgid "Create User Group"
8553 8596 msgstr ""
8554 8597
8555 8598 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:5
8556 8599 msgid "{} user group settings"
8557 8600 msgstr ""
8558 8601
8559 8602 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:37
8560 8603 #: rhodecode/templates/admin/users/user_edit.mako:43
8561 8604 msgid "Global permissions"
8562 8605 msgstr ""
8563 8606
8564 8607 #: rhodecode/templates/admin/user_groups/user_group_edit.mako:38
8565 8608 #: rhodecode/templates/admin/users/user_edit.mako:44
8566 8609 msgid "Permissions summary"
8567 8610 msgstr ""
8568 8611
8569 8612 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:5
8570 8613 msgid "User Group ID"
8571 8614 msgstr ""
8572 8615
8573 8616 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:9
8574 8617 #: rhodecode/templates/admin/user_groups/user_groups.mako:80
8575 8618 #: rhodecode/templates/debug_style/form-elements.html:509
8576 8619 #: rhodecode/templates/user_group/profile.mako:55
8577 8620 msgid "Members"
8578 8621 msgstr ""
8579 8622
8580 8623 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:10
8581 8624 msgid "Automatic member sync"
8582 8625 msgstr ""
8583 8626
8584 8627 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:12
8585 8628 msgid "Assigned to repositories"
8586 8629 msgstr ""
8587 8630
8588 8631 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:13
8589 8632 msgid "Assigned to repo groups"
8590 8633 msgstr ""
8591 8634
8592 8635 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:15
8593 8636 #: rhodecode/templates/admin/users/user_edit_advanced.mako:19
8594 8637 msgid "Assigned to review rules"
8595 8638 msgstr ""
8596 8639
8597 8640 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:22
8598 8641 #: rhodecode/templates/admin/user_groups/user_group_edit_perms.mako:6
8599 8642 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:7
8600 8643 #: rhodecode/templates/data_table/_dt_elements.mako:305
8601 8644 #: rhodecode/templates/user_group/user_group.mako:4
8602 8645 msgid "User group"
8603 8646 msgstr ""
8604 8647
8605 8648 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:35
8606 8649 msgid "Group members sync"
8607 8650 msgstr ""
8608 8651
8609 8652 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:42
8610 8653 msgid "This group is set to be automatically synchronised."
8611 8654 msgstr ""
8612 8655
8613 8656 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:43
8614 8657 msgid "This group synchronization was set by"
8615 8658 msgstr ""
8616 8659
8617 8660 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:47
8618 8661 msgid "This group is not set to be automatically synchronised"
8619 8662 msgstr ""
8620 8663
8621 8664 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:56
8622 8665 msgid "Disable synchronization"
8623 8666 msgstr ""
8624 8667
8625 8668 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:58
8626 8669 msgid "Enable synchronization"
8627 8670 msgstr ""
8628 8671
8629 8672 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:64
8630 8673 msgid "Users will be added or removed from this group when they authenticate with RhodeCode system, based on LDAP group membership. This requires `LDAP+User group` authentication plugin to be configured and enabled. (EE only feature)"
8631 8674 msgstr ""
8632 8675
8633 8676 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:77
8634 8677 msgid "Delete User Group"
8635 8678 msgstr ""
8636 8679
8637 8680 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:83
8638 8681 #, python-format
8639 8682 msgid "Confirm to delete user group `%(ugroup)s` with all permission assignments"
8640 8683 msgstr ""
8641 8684
8642 8685 #: rhodecode/templates/admin/user_groups/user_group_edit_advanced.mako:85
8643 8686 msgid "Delete This User Group"
8644 8687 msgstr ""
8645 8688
8646 8689 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:40
8647 8690 msgid "Change owner of this user group."
8648 8691 msgstr ""
8649 8692
8650 8693 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:64
8651 8694 msgid "Add members"
8652 8695 msgstr ""
8653 8696
8654 8697 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.mako:100
8655 8698 #: rhodecode/templates/user_group/profile.mako:78
8656 8699 msgid "No members yet"
8657 8700 msgstr ""
8658 8701
8659 8702 #: rhodecode/templates/admin/user_groups/user_groups.mako:5
8660 8703 #: rhodecode/templates/admin/users/user_edit_groups.mako:8
8661 8704 msgid "User groups administration"
8662 8705 msgstr ""
8663 8706
8664 8707 #: rhodecode/templates/admin/user_groups/user_groups.mako:82
8665 8708 msgid "Sync"
8666 8709 msgstr ""
8667 8710
8668 8711 #: rhodecode/templates/admin/users/user_add.mako:5
8669 8712 msgid "Add user"
8670 8713 msgstr ""
8671 8714
8672 8715 #: rhodecode/templates/admin/users/user_add.mako:13
8673 8716 #: rhodecode/templates/admin/users/user_edit.mako:14
8674 8717 #: rhodecode/templates/base/base.mako:116
8675 8718 msgid "Users"
8676 8719 msgstr ""
8677 8720
8678 8721 #: rhodecode/templates/admin/users/user_add.mako:15
8679 8722 #: rhodecode/templates/admin/users/users.mako:31
8680 8723 msgid "Add User"
8681 8724 msgstr ""
8682 8725
8683 8726 #: rhodecode/templates/admin/users/user_add.mako:53
8684 8727 msgid "Password confirmation"
8685 8728 msgstr ""
8686 8729
8687 8730 #: rhodecode/templates/admin/users/user_add.mako:59
8688 8731 msgid "Generate password"
8689 8732 msgstr ""
8690 8733
8691 8734 #: rhodecode/templates/admin/users/user_add.mako:106
8692 8735 msgid "Password change"
8693 8736 msgstr ""
8694 8737
8695 8738 #: rhodecode/templates/admin/users/user_add.mako:110
8696 8739 msgid "Force user to change his password on the next login"
8697 8740 msgstr ""
8698 8741
8699 8742 #: rhodecode/templates/admin/users/user_add.mako:116
8700 8743 msgid "Add personal repository group"
8701 8744 msgstr ""
8702 8745
8703 8746 #: rhodecode/templates/admin/users/user_add.mako:121
8704 8747 msgid "New group will be created at: `/{path}`"
8705 8748 msgstr ""
8706 8749
8707 8750 #: rhodecode/templates/admin/users/user_add.mako:122
8708 8751 msgid "User will be automatically set as this group owner."
8709 8752 msgstr ""
8710 8753
8711 8754 #: rhodecode/templates/admin/users/user_add.mako:128
8712 8755 msgid "Create User"
8713 8756 msgstr ""
8714 8757
8715 8758 #: rhodecode/templates/admin/users/user_add.mako:139
8716 8759 msgid "generated password:"
8717 8760 msgstr ""
8718 8761
8719 8762 #: rhodecode/templates/admin/users/user_edit.mako:5
8720 8763 msgid "{} user settings"
8721 8764 msgstr ""
8722 8765
8723 8766 #: rhodecode/templates/admin/users/user_edit.mako:31
8724 8767 msgid "This user is set as non-active and disabled."
8725 8768 msgstr ""
8726 8769
8727 8770 #: rhodecode/templates/admin/users/user_edit.mako:39
8728 8771 #: rhodecode/templates/admin/users/user_edit_profile.mako:7
8729 8772 #: rhodecode/templates/users/user_profile.mako:6
8730 8773 msgid "User Profile"
8731 8774 msgstr ""
8732 8775
8733 8776 #: rhodecode/templates/admin/users/user_edit.mako:40
8734 8777 msgid "Auth tokens"
8735 8778 msgstr ""
8736 8779
8737 8780 #: rhodecode/templates/admin/users/user_edit.mako:46
8738 8781 msgid "Ip Whitelist"
8739 8782 msgstr ""
8740 8783
8741 8784 #: rhodecode/templates/admin/users/user_edit.mako:47
8742 8785 msgid "User Groups Management"
8743 8786 msgstr ""
8744 8787
8745 8788 #: rhodecode/templates/admin/users/user_edit_advanced.mako:5
8746 8789 msgid "User ID"
8747 8790 msgstr ""
8748 8791
8749 8792 #: rhodecode/templates/admin/users/user_edit_advanced.mako:7
8750 8793 msgid "Source of Record"
8751 8794 msgstr ""
8752 8795
8753 8796 #: rhodecode/templates/admin/users/user_edit_advanced.mako:9
8754 8797 msgid "Last login"
8755 8798 msgstr ""
8756 8799
8757 8800 #: rhodecode/templates/admin/users/user_edit_advanced.mako:10
8758 8801 #: rhodecode/templates/admin/users/users.mako:83
8759 8802 #: rhodecode/templates/forks/forks.mako:67
8760 8803 msgid "Last activity"
8761 8804 msgstr ""
8762 8805
8763 8806 #: rhodecode/templates/admin/users/user_edit_advanced.mako:16
8764 8807 msgid "Owned Artifacts"
8765 8808 msgstr ""
8766 8809
8767 8810 #: rhodecode/templates/admin/users/user_edit_advanced.mako:18
8768 8811 msgid "Reviewer of pull requests"
8769 8812 msgstr ""
8770 8813
8771 8814 #: rhodecode/templates/admin/users/user_edit_advanced.mako:21
8772 8815 msgid "Member of User groups"
8773 8816 msgstr ""
8774 8817
8775 8818 #: rhodecode/templates/admin/users/user_edit_advanced.mako:22
8776 8819 msgid "Force password change"
8777 8820 msgstr ""
8778 8821
8779 8822 #: rhodecode/templates/admin/users/user_edit_advanced.mako:49
8780 8823 msgid "Force Password Reset"
8781 8824 msgstr ""
8782 8825
8783 8826 #: rhodecode/templates/admin/users/user_edit_advanced.mako:55
8784 8827 msgid "Disable forced password reset"
8785 8828 msgstr ""
8786 8829
8787 8830 #: rhodecode/templates/admin/users/user_edit_advanced.mako:60
8788 8831 msgid "Clear the forced password change flag."
8789 8832 msgstr ""
8790 8833
8791 8834 #: rhodecode/templates/admin/users/user_edit_advanced.mako:67
8792 8835 msgid "Confirm to enable forced password change"
8793 8836 msgstr ""
8794 8837
8795 8838 #: rhodecode/templates/admin/users/user_edit_advanced.mako:68
8796 8839 msgid "Enable forced password reset"
8797 8840 msgstr ""
8798 8841
8799 8842 #: rhodecode/templates/admin/users/user_edit_advanced.mako:73
8800 8843 msgid "When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface"
8801 8844 msgstr ""
8802 8845
8803 8846 #: rhodecode/templates/admin/users/user_edit_advanced.mako:89
8804 8847 msgid "Users personal repository group"
8805 8848 msgstr ""
8806 8849
8807 8850 #: rhodecode/templates/admin/users/user_edit_advanced.mako:92
8808 8851 msgid "This user currently does not have a personal repository group"
8809 8852 msgstr ""
8810 8853
8811 8854 #: rhodecode/templates/admin/users/user_edit_advanced.mako:94
8812 8855 #, python-format
8813 8856 msgid "New group will be created at: `/%(path)s`"
8814 8857 msgstr ""
8815 8858
8816 8859 #: rhodecode/templates/admin/users/user_edit_advanced.mako:99
8817 8860 msgid "Create personal repository group"
8818 8861 msgstr ""
8819 8862
8820 8863 #: rhodecode/templates/admin/users/user_edit_advanced.mako:108
8821 8864 msgid "Delete User"
8822 8865 msgstr ""
8823 8866
8824 8867 #: rhodecode/templates/admin/users/user_edit_advanced.mako:119
8825 8868 msgid "Detach repositories"
8826 8869 msgstr ""
8827 8870
8828 8871 #: rhodecode/templates/admin/users/user_edit_advanced.mako:122
8829 8872 #: rhodecode/templates/admin/users/user_edit_advanced.mako:134
8830 8873 #: rhodecode/templates/admin/users/user_edit_advanced.mako:146
8831 8874 msgid "Delete repositories"
8832 8875 msgstr ""
8833 8876
8834 8877 #: rhodecode/templates/admin/users/user_edit_advanced.mako:131
8835 8878 msgid "Detach repository groups"
8836 8879 msgstr ""
8837 8880
8838 8881 #: rhodecode/templates/admin/users/user_edit_advanced.mako:143
8839 8882 msgid "Detach user groups"
8840 8883 msgstr ""
8841 8884
8842 8885 #: rhodecode/templates/admin/users/user_edit_advanced.mako:155
8843 8886 msgid "Detach pull requests"
8844 8887 msgstr ""
8845 8888
8846 8889 #: rhodecode/templates/admin/users/user_edit_advanced.mako:158
8847 8890 msgid "Delete pull requests"
8848 8891 msgstr ""
8849 8892
8850 8893 #: rhodecode/templates/admin/users/user_edit_advanced.mako:167
8851 8894 msgid "Detach Artifacts"
8852 8895 msgstr ""
8853 8896
8854 8897 #: rhodecode/templates/admin/users/user_edit_advanced.mako:170
8855 8898 msgid "Delete Artifacts"
8856 8899 msgstr ""
8857 8900
8858 8901 #: rhodecode/templates/admin/users/user_edit_advanced.mako:180
8859 8902 msgid "New owner for detached objects"
8860 8903 msgstr ""
8861 8904
8862 8905 #: rhodecode/templates/admin/users/user_edit_advanced.mako:188
8863 8906 msgid "When selecting the detach option, the depending objects owned by this user will be assigned to the above user."
8864 8907 msgstr ""
8865 8908
8866 8909 #: rhodecode/templates/admin/users/user_edit_advanced.mako:190
8867 8910 msgid "The delete option will delete the user and all his owned objects!"
8868 8911 msgstr ""
8869 8912
8870 8913 #: rhodecode/templates/admin/users/user_edit_advanced.mako:205
8871 8914 msgid "Delete this user"
8872 8915 msgstr ""
8873 8916
8874 8917 #: rhodecode/templates/admin/users/user_edit_audit.mako:9
8875 8918 msgid "Audit Logs"
8876 8919 msgstr ""
8877 8920
8878 8921 #: rhodecode/templates/admin/users/user_edit_audit.mako:12
8879 8922 msgid "Download as JSON"
8880 8923 msgstr ""
8881 8924
8882 8925 #: rhodecode/templates/admin/users/user_edit_auth_tokens.mako:68
8883 8926 #, python-format
8884 8927 msgid "Confirm to remove this auth token: %s"
8885 8928 msgstr ""
8886 8929
8887 8930 #: rhodecode/templates/admin/users/user_edit_caches.mako:36
8888 8931 msgid "Invalidate user cache"
8889 8932 msgstr ""
8890 8933
8891 8934 #: rhodecode/templates/admin/users/user_edit_caches.mako:36
8892 8935 msgid "Confirm to invalidate user cache"
8893 8936 msgstr ""
8894 8937
8895 8938 #: rhodecode/templates/admin/users/user_edit_emails.mako:7
8896 8939 msgid "Additional Email Addresses"
8897 8940 msgstr ""
8898 8941
8899 8942 #: rhodecode/templates/admin/users/user_edit_emails.mako:33
8900 8943 #, python-format
8901 8944 msgid "Confirm to delete this email: %s"
8902 8945 msgstr ""
8903 8946
8904 8947 #: rhodecode/templates/admin/users/user_edit_emails.mako:58
8905 8948 msgid "New email address"
8906 8949 msgstr ""
8907 8950
8908 8951 #: rhodecode/templates/admin/users/user_edit_groups.mako:15
8909 8952 #, python-format
8910 8953 msgid "Add `%s` to user group"
8911 8954 msgstr ""
8912 8955
8913 8956 #: rhodecode/templates/admin/users/user_edit_ips.mako:7
8914 8957 msgid "Custom IP Whitelist"
8915 8958 msgstr ""
8916 8959
8917 8960 #: rhodecode/templates/admin/users/user_edit_ips.mako:15
8918 8961 msgid "IP Address"
8919 8962 msgstr ""
8920 8963
8921 8964 #: rhodecode/templates/admin/users/user_edit_ips.mako:16
8922 8965 msgid "IP Range"
8923 8966 msgstr ""
8924 8967
8925 8968 #: rhodecode/templates/admin/users/user_edit_ips.mako:25
8926 8969 #, python-format
8927 8970 msgid "Inherited from %s"
8928 8971 msgstr ""
8929 8972
8930 8973 #: rhodecode/templates/admin/users/user_edit_ips.mako:69
8931 8974 msgid ""
8932 8975 "Enter comma separated list of ip addresses like 10.0.0.1,10.0.0.2.\n"
8933 8976 "Use a ip address with a mask 127.0.0.1/24, to create a network match pattern.\n"
8934 8977 "To specify multiple entries on an address range use 127.0.0.1-127.0.0.10 syntax"
8935 8978 msgstr ""
8936 8979
8937 8980 #: rhodecode/templates/admin/users/user_edit_profile.mako:31
8938 8981 msgid "Change the avatar at"
8939 8982 msgstr ""
8940 8983
8941 8984 #: rhodecode/templates/admin/users/user_edit_profile.mako:94
8942 8985 msgid "New Password"
8943 8986 msgstr ""
8944 8987
8945 8988 #: rhodecode/templates/admin/users/user_edit_profile.mako:102
8946 8989 msgid "New Password Confirmation"
8947 8990 msgstr ""
8948 8991
8949 8992 #: rhodecode/templates/admin/users/user_edit_profile.mako:118
8950 8993 #: rhodecode/templates/admin/users/users.mako:87
8951 8994 #: rhodecode/templates/base/perms_summary.mako:150
8952 8995 msgid "Super-admin"
8953 8996 msgstr ""
8954 8997
8955 8998 #: rhodecode/templates/admin/users/user_edit_profile.mako:126
8956 8999 msgid "Authentication type"
8957 9000 msgstr ""
8958 9001
8959 9002 #: rhodecode/templates/admin/users/user_edit_profile.mako:130
8960 9003 msgid "When user was created using an external source. He is bound to authentication using this method."
8961 9004 msgstr ""
8962 9005
8963 9006 #: rhodecode/templates/admin/users/user_edit_profile.mako:135
8964 9007 msgid "Name in Source of Record"
8965 9008 msgstr ""
8966 9009
8967 9010 #: rhodecode/templates/admin/users/user_edit_profile.mako:144
8968 9011 msgid "Language"
8969 9012 msgstr ""
8970 9013
8971 9014 #: rhodecode/templates/admin/users/user_edit_profile.mako:150
8972 9015 #, python-format
8973 9016 msgid "User interface language. Help translate %(rc_link)s into your language."
8974 9017 msgstr ""
8975 9018
8976 9019 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:34
8977 9020 #, python-format
8978 9021 msgid "Confirm to remove ssh key %s"
8979 9022 msgstr ""
8980 9023
8981 9024 #: rhodecode/templates/admin/users/user_edit_ssh_keys.mako:75
8982 9025 msgid "Click add to use this generate SSH key"
8983 9026 msgstr ""
8984 9027
8985 9028 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:7
8986 9029 msgid "New SSH Key generation"
8987 9030 msgstr ""
8988 9031
8989 9032 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:13
8990 9033 msgid "Below is a 2048 bit generated SSH RSA key."
8991 9034 msgstr ""
8992 9035
8993 9036 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:13
8994 9037 msgid "If you use older systems please try to generate a"
8995 9038 msgstr ""
8996 9039
8997 9040 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:13
8998 9041 msgid "legacy format"
8999 9042 msgstr ""
9000 9043
9001 9044 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:14
9002 9045 msgid "If You wish to use it to access RhodeCode via the SSH please save the private key and click `Use this generated key` at the bottom."
9003 9046 msgstr ""
9004 9047
9005 9048 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:16
9006 9049 msgid "Private key"
9007 9050 msgstr ""
9008 9051
9009 9052 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:32
9010 9053 msgid "Public key"
9011 9054 msgstr ""
9012 9055
9013 9056 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:43
9014 9057 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:45
9015 9058 msgid "Use this generated key"
9016 9059 msgstr ""
9017 9060
9018 9061 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:47
9019 9062 msgid "Confirmation required on the next screen"
9020 9063 msgstr ""
9021 9064
9022 9065 #: rhodecode/templates/admin/users/user_edit_ssh_keys_generate.mako:51
9023 9066 msgid "SSH key generator has been disabled."
9024 9067 msgstr ""
9025 9068
9026 9069 #: rhodecode/templates/admin/users/users.mako:5
9027 9070 msgid "Users administration"
9028 9071 msgstr ""
9029 9072
9030 9073 #: rhodecode/templates/admin/users/users.mako:89
9031 9074 msgid "Auth type"
9032 9075 msgstr ""
9033 9076
9034 9077 #: rhodecode/templates/artifacts/artifact_list.mako:5
9035 9078 msgid "{} Artifacts"
9036 9079 msgstr ""
9037 9080
9038 9081 #: rhodecode/templates/base/base.mako:73
9039 9082 msgid "RhodeCode instance id: {}"
9040 9083 msgstr ""
9041 9084
9042 9085 #: rhodecode/templates/base/base.mako:101
9043 9086 msgid "Super-admin Panel"
9044 9087 msgstr ""
9045 9088
9046 9089 #: rhodecode/templates/base/base.mako:103
9047 9090 msgid "Delegated Admin Panel"
9048 9091 msgstr ""
9049 9092
9050 9093 #: rhodecode/templates/base/base.mako:119
9051 9094 msgid "Authentication"
9052 9095 msgstr ""
9053 9096
9054 9097 #: rhodecode/templates/base/base.mako:121
9055 9098 msgid "Defaults"
9056 9099 msgstr ""
9057 9100
9058 9101 #: rhodecode/templates/base/base.mako:161
9059 9102 #: rhodecode/templates/base/base.mako:201
9060 9103 #: rhodecode/templates/changeset/changeset.mako:140
9061 9104 #: rhodecode/templates/files/files_source_header.mako:57
9062 9105 #: rhodecode/templates/files/files_tree_header.mako:44
9063 9106 #: rhodecode/templates/summary/components.mako:274
9064 9107 msgid "Show More"
9065 9108 msgstr ""
9066 9109
9067 9110 #: rhodecode/templates/base/base.mako:304
9068 9111 #: rhodecode/templates/base/base.mako:315
9069 9112 msgid "RSS Feed"
9070 9113 msgstr ""
9071 9114
9072 9115 #: rhodecode/templates/base/base.mako:306
9073 9116 msgid "Watch this Repository and actions on it in your personalized journal"
9074 9117 msgstr ""
9075 9118
9076 9119 #: rhodecode/templates/base/base.mako:324
9077 9120 msgid "Fork of"
9078 9121 msgstr ""
9079 9122
9080 9123 #: rhodecode/templates/base/base.mako:341
9081 9124 #, python-format
9082 9125 msgid "Repository locked by %(user)s"
9083 9126 msgstr ""
9084 9127
9085 9128 #: rhodecode/templates/base/base.mako:346
9086 9129 msgid "Repository not locked. Pull repository to lock it."
9087 9130 msgstr ""
9088 9131
9089 9132 #: rhodecode/templates/base/base.mako:362
9090 9133 msgid "This repository has been archived. It is now read-only."
9091 9134 msgstr ""
9092 9135
9093 9136 #: rhodecode/templates/base/base.mako:375
9094 9137 #: rhodecode/templates/data_table/_dt_elements.mako:58
9095 9138 #: rhodecode/templates/data_table/_dt_elements.mako:59
9096 9139 #: rhodecode/templates/data_table/_dt_elements.mako:207
9097 9140 msgid "Summary"
9098 9141 msgstr ""
9099 9142
9100 9143 #: rhodecode/templates/base/base.mako:376
9101 9144 #: rhodecode/templates/data_table/_dt_elements.mako:63
9102 9145 #: rhodecode/templates/data_table/_dt_elements.mako:64
9103 9146 #: rhodecode/templates/files/file_authors_box.mako:30
9104 9147 #: rhodecode/templates/search/search.mako:99
9105 9148 #: rhodecode/templates/summary/components.mako:119
9106 9149 #: rhodecode/templates/summary/components.mako:127
9107 9150 msgid "Commits"
9108 9151 msgstr ""
9109 9152
9110 9153 #: rhodecode/templates/base/base.mako:377
9111 9154 #: rhodecode/templates/data_table/_dt_elements.mako:68
9112 9155 #: rhodecode/templates/data_table/_dt_elements.mako:69
9113 9156 #: rhodecode/templates/files/files.mako:15
9114 9157 #: rhodecode/templates/search/search.mako:99
9115 9158 msgid "Files"
9116 9159 msgstr ""
9117 9160
9118 9161 #: rhodecode/templates/base/base.mako:378
9119 9162 #: rhodecode/templates/bookmarks/bookmarks.mako:78
9120 9163 #: rhodecode/templates/branches/branches.mako:77
9121 9164 #: rhodecode/templates/tags/tags.mako:78
9122 9165 msgid "Compare"
9123 9166 msgstr ""
9124 9167
9125 9168 #: rhodecode/templates/base/base.mako:383
9126 9169 #, python-format
9127 9170 msgid "Show Pull Requests for %s"
9128 9171 msgstr ""
9129 9172
9130 9173 #: rhodecode/templates/base/base.mako:394
9131 9174 msgid "Artifacts"
9132 9175 msgstr ""
9133 9176
9134 9177 #: rhodecode/templates/base/base.mako:400
9135 9178 msgid "Repository Settings"
9136 9179 msgstr ""
9137 9180
9138 9181 #: rhodecode/templates/base/base.mako:406
9139 9182 msgid "Options"
9140 9183 msgstr ""
9141 9184
9142 9185 #: rhodecode/templates/base/base.mako:411
9143 9186 msgid "Unlock Repository"
9144 9187 msgstr ""
9145 9188
9146 9189 #: rhodecode/templates/base/base.mako:413
9147 9190 msgid "Lock Repository"
9148 9191 msgstr ""
9149 9192
9150 9193 #: rhodecode/templates/base/base.mako:466
9151 9194 msgid "Group Home"
9152 9195 msgstr ""
9153 9196
9154 9197 #: rhodecode/templates/base/base.mako:470
9155 9198 msgid "You have admin right to this group, and can edit it"
9156 9199 msgstr ""
9157 9200
9158 9201 #: rhodecode/templates/base/base.mako:470
9159 9202 msgid "Group Settings"
9160 9203 msgstr ""
9161 9204
9162 9205 #: rhodecode/templates/base/base.mako:521
9163 9206 msgid "This Repository"
9164 9207 msgstr ""
9165 9208
9166 9209 #: rhodecode/templates/base/base.mako:523
9167 9210 msgid "Create Pull Request"
9168 9211 msgstr ""
9169 9212
9170 9213 #: rhodecode/templates/base/base.mako:527
9171 9214 msgid "Fork this repository"
9172 9215 msgstr ""
9173 9216
9174 9217 #: rhodecode/templates/base/base.mako:534
9175 9218 msgid "This Repository Group"
9176 9219 msgstr ""
9177 9220
9178 9221 #: rhodecode/templates/base/base.mako:538
9179 9222 #: rhodecode/templates/base/base.mako:554
9180 9223 #: rhodecode/templates/base/base.mako:566
9181 9224 msgid "New Repository"
9182 9225 msgstr ""
9183 9226
9184 9227 #: rhodecode/templates/base/base.mako:544
9185 9228 #: rhodecode/templates/base/base.mako:558
9186 9229 #: rhodecode/templates/base/base.mako:572
9187 9230 msgid "New Repository Group"
9188 9231 msgstr ""
9189 9232
9190 9233 #: rhodecode/templates/base/base.mako:599
9191 9234 msgid "Sign in"
9192 9235 msgstr ""
9193 9236
9194 9237 #: rhodecode/templates/base/base.mako:624
9195 9238 msgid "My personal group"
9196 9239 msgstr ""
9197 9240
9198 9241 #: rhodecode/templates/base/base.mako:630
9199 9242 #: rhodecode/templates/debug_style/alerts.html:5
9200 9243 #: rhodecode/templates/debug_style/buttons.html:5
9201 9244 #: rhodecode/templates/debug_style/code-block.html:6
9202 9245 #: rhodecode/templates/debug_style/collapsable-content.html:5
9203 9246 #: rhodecode/templates/debug_style/emails.html:5
9204 9247 #: rhodecode/templates/debug_style/form-elements-small.html:5
9205 9248 #: rhodecode/templates/debug_style/form-elements.html:5
9206 9249 #: rhodecode/templates/debug_style/form-inline.html:5
9207 9250 #: rhodecode/templates/debug_style/form-vertical.html:5
9208 9251 #: rhodecode/templates/debug_style/forms.html:5
9209 9252 #: rhodecode/templates/debug_style/icons.html:5
9210 9253 #: rhodecode/templates/debug_style/index.html:12
9211 9254 #: rhodecode/templates/debug_style/labels.html:5
9212 9255 #: rhodecode/templates/debug_style/layout-form-sidebar.html:5
9213 9256 #: rhodecode/templates/debug_style/login.html:6
9214 9257 #: rhodecode/templates/debug_style/panels.html:5
9215 9258 #: rhodecode/templates/debug_style/tables-wide.html:5
9216 9259 #: rhodecode/templates/debug_style/tables.html:5
9217 9260 #: rhodecode/templates/debug_style/typography.html:5
9218 9261 msgid "Style"
9219 9262 msgstr ""
9220 9263
9221 9264 #: rhodecode/templates/base/base.mako:631
9222 9265 msgid "[Style]"
9223 9266 msgstr ""
9224 9267
9225 9268 #: rhodecode/templates/base/base.mako:648
9226 9269 msgid "No Bookmarks yet."
9227 9270 msgstr ""
9228 9271
9229 9272 #: rhodecode/templates/base/base.mako:686
9230 9273 msgid "Sign Out"
9231 9274 msgstr ""
9232 9275
9233 9276 #: rhodecode/templates/base/base.mako:731
9277 #: rhodecode/templates/changeset/changeset_file_comment.mako:538
9234 9278 msgid "dismiss"
9235 9279 msgstr ""
9236 9280
9237 9281 #: rhodecode/templates/base/base.mako:772
9238 9282 msgid "search / go to..."
9239 9283 msgstr ""
9240 9284
9241 9285 #: rhodecode/templates/base/base.mako:817
9242 9286 msgid "Show activity journal"
9243 9287 msgstr ""
9244 9288
9245 9289 #: rhodecode/templates/base/base.mako:818
9246 9290 #: rhodecode/templates/journal/journal.mako:4
9247 9291 #: rhodecode/templates/journal/journal.mako:14
9248 9292 msgid "Journal"
9249 9293 msgstr ""
9250 9294
9251 9295 #: rhodecode/templates/base/base.mako:823
9252 9296 msgid "Show Public activity journal"
9253 9297 msgstr ""
9254 9298
9255 9299 #: rhodecode/templates/base/base.mako:824
9256 9300 msgid "Public journal"
9257 9301 msgstr ""
9258 9302
9259 9303 #: rhodecode/templates/base/base.mako:830
9260 9304 msgid "Show Gists"
9261 9305 msgstr ""
9262 9306
9263 9307 #: rhodecode/templates/base/base.mako:831
9264 9308 msgid "Gists"
9265 9309 msgstr ""
9266 9310
9267 9311 #: rhodecode/templates/base/base.mako:837
9268 9312 msgid "Admin settings"
9269 9313 msgstr ""
9270 9314
9271 9315 #: rhodecode/templates/base/base.mako:1154
9272 9316 msgid "Keyboard shortcuts"
9273 9317 msgstr ""
9274 9318
9275 9319 #: rhodecode/templates/base/base.mako:1162
9276 9320 msgid "Site-wide shortcuts"
9277 9321 msgstr ""
9278 9322
9279 9323 #: rhodecode/templates/base/default_perms_box.mako:19
9280 9324 msgid "Inherited Permissions"
9281 9325 msgstr ""
9282 9326
9283 9327 #: rhodecode/templates/base/default_perms_box.mako:25
9284 9328 msgid "Custom Permissions"
9285 9329 msgstr ""
9286 9330
9287 9331 #: rhodecode/templates/base/default_perms_box.mako:29
9288 9332 msgid "Default Global Permissions"
9289 9333 msgstr ""
9290 9334
9291 9335 #: rhodecode/templates/base/default_perms_box.mako:36
9292 9336 msgid "The following options configure the default permissions each user or group will inherit. You can override these permissions for each individual user or user group using individual permissions settings."
9293 9337 msgstr ""
9294 9338
9295 9339 #: rhodecode/templates/base/default_perms_box.mako:40
9296 9340 msgid "Repository Creation"
9297 9341 msgstr ""
9298 9342
9299 9343 #: rhodecode/templates/base/default_perms_box.mako:45
9300 9344 msgid "Permission to create root level repositories. When disabled, users can still create repositories inside their own repository groups."
9301 9345 msgstr ""
9302 9346
9303 9347 #: rhodecode/templates/base/default_perms_box.mako:50
9304 9348 msgid "Repository Creation With Group Write Access"
9305 9349 msgstr ""
9306 9350
9307 9351 #: rhodecode/templates/base/default_perms_box.mako:55
9308 9352 msgid "Write permission given on a repository group will allow creating repositories inside that group."
9309 9353 msgstr ""
9310 9354
9311 9355 #: rhodecode/templates/base/default_perms_box.mako:60
9312 9356 msgid "Repository Forking"
9313 9357 msgstr ""
9314 9358
9315 9359 #: rhodecode/templates/base/default_perms_box.mako:65
9316 9360 msgid "Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups."
9317 9361 msgstr ""
9318 9362
9319 9363 #: rhodecode/templates/base/default_perms_box.mako:70
9320 9364 msgid "Repository Group Creation"
9321 9365 msgstr ""
9322 9366
9323 9367 #: rhodecode/templates/base/default_perms_box.mako:75
9324 9368 msgid "Permission to create root level repository groups. When disabled, repository group admins can still create repository subgroups within their repository groups."
9325 9369 msgstr ""
9326 9370
9327 9371 #: rhodecode/templates/base/default_perms_box.mako:80
9328 9372 msgid "User Group Creation"
9329 9373 msgstr ""
9330 9374
9331 9375 #: rhodecode/templates/base/default_perms_box.mako:85
9332 9376 msgid "Permission to allow user group creation."
9333 9377 msgstr ""
9334 9378
9335 9379 #: rhodecode/templates/base/default_perms_box.mako:91
9336 9380 msgid "Inherit Permissions From The Default User"
9337 9381 msgstr ""
9338 9382
9339 9383 #: rhodecode/templates/base/default_perms_box.mako:96
9340 9384 msgid "Inherit default permissions from the default user. Turn off this option to force explicit permissions for users, even if they are more restrictive than the default user permissions."
9341 9385 msgstr ""
9342 9386
9343 9387 #: rhodecode/templates/base/default_perms_box.mako:115
9344 9388 msgid "Inherit from default settings"
9345 9389 msgstr ""
9346 9390
9347 9391 #: rhodecode/templates/base/default_perms_box.mako:120
9348 9392 #, python-format
9349 9393 msgid ""
9350 9394 "Select to inherit permissions from %s permissions settings, including default IP address whitelist and inheritance of \n"
9351 9395 "permission by members of user groups."
9352 9396 msgstr ""
9353 9397
9354 9398 #: rhodecode/templates/base/issue_tracker_settings.mako:82
9355 9399 msgid "Extra Prefix"
9356 9400 msgstr ""
9357 9401
9358 9402 #: rhodecode/templates/base/issue_tracker_settings.mako:93
9359 9403 msgid "show examples"
9360 9404 msgstr ""
9361 9405
9362 9406 #: rhodecode/templates/base/issue_tracker_settings.mako:161
9363 9407 msgid "Add new"
9364 9408 msgstr ""
9365 9409
9366 9410 #: rhodecode/templates/base/issue_tracker_settings.mako:169
9367 9411 msgid "New Entry"
9368 9412 msgstr ""
9369 9413
9370 9414 #: rhodecode/templates/base/issue_tracker_settings.mako:173
9371 9415 msgid "Confirm to remove this pattern:"
9372 9416 msgstr ""
9373 9417
9374 9418 #: rhodecode/templates/base/issue_tracker_settings.mako:300
9375 9419 #: rhodecode/templates/changeset/changeset_file_comment.mako:395
9376 9420 #: rhodecode/templates/changeset/changeset_file_comment.mako:446
9377 9421 #: rhodecode/templates/data_table/_dt_elements.mako:456
9378 9422 #: rhodecode/templates/files/files_add.mako:59
9379 9423 #: rhodecode/templates/files/files_edit.mako:61
9380 9424 msgid "Preview"
9381 9425 msgstr ""
9382 9426
9383 9427 #: rhodecode/templates/base/issue_tracker_settings.mako:301
9384 9428 msgid "Test Pattern Preview"
9385 9429 msgstr ""
9386 9430
9387 9431 #: rhodecode/templates/base/perms_summary.mako:31
9388 9432 msgid "in JSON format"
9389 9433 msgstr ""
9390 9434
9391 9435 #: rhodecode/templates/base/perms_summary.mako:40
9392 9436 #: rhodecode/templates/base/perms_summary.mako:48
9393 9437 msgid "show"
9394 9438 msgstr ""
9395 9439
9396 9440 #: rhodecode/templates/base/perms_summary.mako:41
9397 9441 #: rhodecode/templates/base/perms_summary.mako:49
9398 9442 msgid "none"
9399 9443 msgstr ""
9400 9444
9401 9445 #: rhodecode/templates/base/perms_summary.mako:42
9402 9446 #: rhodecode/templates/commits/changelog_elements.mako:102
9403 9447 #: rhodecode/templates/files/base.mako:26
9404 9448 msgid "merge"
9405 9449 msgstr ""
9406 9450
9407 9451 #: rhodecode/templates/base/perms_summary.mako:43
9408 9452 msgid "push"
9409 9453 msgstr ""
9410 9454
9411 9455 #: rhodecode/templates/base/perms_summary.mako:44
9412 9456 msgid "push force"
9413 9457 msgstr ""
9414 9458
9415 9459 #: rhodecode/templates/base/perms_summary.mako:50
9416 9460 msgid "read"
9417 9461 msgstr ""
9418 9462
9419 9463 #: rhodecode/templates/base/perms_summary.mako:51
9420 9464 msgid "write"
9421 9465 msgstr ""
9422 9466
9423 9467 #: rhodecode/templates/base/perms_summary.mako:52
9424 9468 msgid "admin"
9425 9469 msgstr ""
9426 9470
9427 9471 #: rhodecode/templates/base/perms_summary.mako:60
9428 9472 msgid "No permissions defined"
9429 9473 msgstr ""
9430 9474
9431 9475 #: rhodecode/templates/base/perms_summary.mako:68
9432 9476 #: rhodecode/templates/base/perms_summary.mako:175
9433 9477 #: rhodecode/templates/base/perms_summary.mako:248
9434 9478 msgid "Permission"
9435 9479 msgstr ""
9436 9480
9437 9481 #: rhodecode/templates/base/perms_summary.mako:70
9438 9482 #: rhodecode/templates/base/perms_summary.mako:250
9439 9483 msgid "Edit Permission"
9440 9484 msgstr ""
9441 9485
9442 9486 #: rhodecode/templates/base/perms_summary.mako:124
9443 9487 #: rhodecode/templates/base/perms_summary.mako:133
9444 9488 msgid "edit global"
9445 9489 msgstr ""
9446 9490
9447 9491 #: rhodecode/templates/base/perms_summary.mako:141
9448 9492 msgid "Repository default permission"
9449 9493 msgstr ""
9450 9494
9451 9495 #: rhodecode/templates/base/perms_summary.mako:144
9452 9496 msgid "Repository group default permission"
9453 9497 msgstr ""
9454 9498
9455 9499 #: rhodecode/templates/base/perms_summary.mako:147
9456 9500 msgid "User group default permission"
9457 9501 msgstr ""
9458 9502
9459 9503 #: rhodecode/templates/base/perms_summary.mako:153
9460 9504 msgid "Inherit permissions"
9461 9505 msgstr ""
9462 9506
9463 9507 #: rhodecode/templates/base/perms_summary.mako:156
9464 9508 msgid "Create repositories"
9465 9509 msgstr ""
9466 9510
9467 9511 #: rhodecode/templates/base/perms_summary.mako:159
9468 9512 msgid "Fork repositories"
9469 9513 msgstr ""
9470 9514
9471 9515 #: rhodecode/templates/base/perms_summary.mako:162
9472 9516 msgid "Create repository groups"
9473 9517 msgstr ""
9474 9518
9475 9519 #: rhodecode/templates/base/perms_summary.mako:165
9476 9520 msgid "Create user groups"
9477 9521 msgstr ""
9478 9522
9479 9523 #: rhodecode/templates/base/perms_summary.mako:177
9480 9524 msgid "Edit Branch Permission"
9481 9525 msgstr ""
9482 9526
9483 9527 #: rhodecode/templates/base/perms_summary.mako:291
9484 9528 msgid "overridden by"
9485 9529 msgstr ""
9486 9530
9487 9531 #: rhodecode/templates/base/perms_summary.mako:326
9488 9532 msgid "No matching permission defined"
9489 9533 msgstr ""
9490 9534
9491 9535 #: rhodecode/templates/base/root.mako:156
9492 9536 msgid "Please enable JavaScript to use RhodeCode Enterprise"
9493 9537 msgstr ""
9494 9538
9495 9539 #: rhodecode/templates/base/vcs_settings.mako:16
9496 9540 msgid "Require SSL for vcs operations"
9497 9541 msgstr ""
9498 9542
9499 9543 #: rhodecode/templates/base/vcs_settings.mako:19
9500 9544 msgid "Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable."
9501 9545 msgstr ""
9502 9546
9503 9547 #: rhodecode/templates/base/vcs_settings.mako:29
9504 9548 msgid "Main Storage Location"
9505 9549 msgstr ""
9506 9550
9507 9551 #: rhodecode/templates/base/vcs_settings.mako:37
9508 9552 msgid "Click to unlock. You must restart RhodeCode in order to make this setting take effect."
9509 9553 msgstr ""
9510 9554
9511 9555 #: rhodecode/templates/base/vcs_settings.mako:41
9512 9556 msgid "Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file."
9513 9557 msgstr ""
9514 9558
9515 9559 #: rhodecode/templates/base/vcs_settings.mako:48
9516 9560 msgid "Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required."
9517 9561 msgstr ""
9518 9562
9519 9563 #: rhodecode/templates/base/vcs_settings.mako:57
9520 9564 msgid "Internal Hooks"
9521 9565 msgstr ""
9522 9566
9523 9567 #: rhodecode/templates/base/vcs_settings.mako:63
9524 9568 msgid "Show repository size after push"
9525 9569 msgstr ""
9526 9570
9527 9571 #: rhodecode/templates/base/vcs_settings.mako:67
9528 9572 msgid "Trigger a hook that calculates repository size after each push."
9529 9573 msgstr ""
9530 9574
9531 9575 #: rhodecode/templates/base/vcs_settings.mako:71
9532 9576 msgid "Execute pre/post push hooks"
9533 9577 msgstr ""
9534 9578
9535 9579 #: rhodecode/templates/base/vcs_settings.mako:74
9536 9580 msgid "Execute Built in pre/post push hooks. This also executes rcextensions hooks."
9537 9581 msgstr ""
9538 9582
9539 9583 #: rhodecode/templates/base/vcs_settings.mako:78
9540 9584 msgid "Execute pre/post pull hooks"
9541 9585 msgstr ""
9542 9586
9543 9587 #: rhodecode/templates/base/vcs_settings.mako:81
9544 9588 msgid "Execute Built in pre/post pull hooks. This also executes rcextensions hooks."
9545 9589 msgstr ""
9546 9590
9547 9591 #: rhodecode/templates/base/vcs_settings.mako:91
9548 9592 msgid "Mercurial Settings"
9549 9593 msgstr ""
9550 9594
9551 9595 #: rhodecode/templates/base/vcs_settings.mako:96
9552 9596 msgid "Enable largefiles extension"
9553 9597 msgstr ""
9554 9598
9555 9599 #: rhodecode/templates/base/vcs_settings.mako:100
9556 9600 msgid "Enable Largefiles extensions for all repositories."
9557 9601 msgstr ""
9558 9602
9559 9603 #: rhodecode/templates/base/vcs_settings.mako:102
9560 9604 msgid "Enable Largefiles extensions for this repository."
9561 9605 msgstr ""
9562 9606
9563 9607 #: rhodecode/templates/base/vcs_settings.mako:113
9564 9608 msgid "Filesystem location where Mercurial largefile objects should be stored."
9565 9609 msgstr ""
9566 9610
9567 9611 #: rhodecode/templates/base/vcs_settings.mako:119
9568 9612 msgid "Set repositories as publishing"
9569 9613 msgstr ""
9570 9614
9571 9615 #: rhodecode/templates/base/vcs_settings.mako:119
9572 9616 msgid "Set repository as publishing"
9573 9617 msgstr ""
9574 9618
9575 9619 #: rhodecode/templates/base/vcs_settings.mako:122
9576 9620 msgid "When this is enabled all commits in the repository are seen as public commits by clients."
9577 9621 msgstr ""
9578 9622
9579 9623 #: rhodecode/templates/base/vcs_settings.mako:127
9580 9624 msgid "Enable hgsubversion extension"
9581 9625 msgstr ""
9582 9626
9583 9627 #: rhodecode/templates/base/vcs_settings.mako:130
9584 9628 msgid "Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type."
9585 9629 msgstr ""
9586 9630
9587 9631 #: rhodecode/templates/base/vcs_settings.mako:136
9588 9632 msgid "Enable Evolve and Topic extension"
9589 9633 msgstr ""
9590 9634
9591 9635 #: rhodecode/templates/base/vcs_settings.mako:140
9592 9636 msgid "Enable Evolve and Topic extensions for all repositories."
9593 9637 msgstr ""
9594 9638
9595 9639 #: rhodecode/templates/base/vcs_settings.mako:142
9596 9640 msgid "Enable Evolve and Topic extensions for this repository."
9597 9641 msgstr ""
9598 9642
9599 9643 #: rhodecode/templates/base/vcs_settings.mako:153
9600 9644 msgid "Git Settings"
9601 9645 msgstr ""
9602 9646
9603 9647 #: rhodecode/templates/base/vcs_settings.mako:158
9604 9648 msgid "Enable lfs extension"
9605 9649 msgstr ""
9606 9650
9607 9651 #: rhodecode/templates/base/vcs_settings.mako:162
9608 9652 msgid "Enable lfs extensions for all repositories."
9609 9653 msgstr ""
9610 9654
9611 9655 #: rhodecode/templates/base/vcs_settings.mako:164
9612 9656 msgid "Enable lfs extensions for this repository."
9613 9657 msgstr ""
9614 9658
9615 9659 #: rhodecode/templates/base/vcs_settings.mako:175
9616 9660 msgid "Filesystem location where Git lfs objects should be stored."
9617 9661 msgstr ""
9618 9662
9619 9663 #: rhodecode/templates/base/vcs_settings.mako:186
9620 9664 msgid "Global Subversion Settings"
9621 9665 msgstr ""
9622 9666
9623 9667 #: rhodecode/templates/base/vcs_settings.mako:192
9624 9668 msgid "Proxy subversion HTTP requests"
9625 9669 msgstr ""
9626 9670
9627 9671 #: rhodecode/templates/base/vcs_settings.mako:196
9628 9672 msgid "Subversion HTTP Support. Enables communication with SVN over HTTP protocol."
9629 9673 msgstr ""
9630 9674
9631 9675 #: rhodecode/templates/base/vcs_settings.mako:197
9632 9676 msgid "SVN Protocol setup Documentation"
9633 9677 msgstr ""
9634 9678
9635 9679 #: rhodecode/templates/base/vcs_settings.mako:203
9636 9680 msgid "Subversion HTTP Server URL"
9637 9681 msgstr ""
9638 9682
9639 9683 #: rhodecode/templates/base/vcs_settings.mako:209
9640 9684 msgid "Generate Apache Config"
9641 9685 msgstr ""
9642 9686
9643 9687 #: rhodecode/templates/base/vcs_settings.mako:221
9644 9688 msgid "Subversion Settings"
9645 9689 msgstr ""
9646 9690
9647 9691 #: rhodecode/templates/base/vcs_settings.mako:226
9648 9692 msgid "Repository patterns"
9649 9693 msgstr ""
9650 9694
9651 9695 #: rhodecode/templates/base/vcs_settings.mako:230
9652 9696 msgid "Patterns for identifying SVN branches and tags. For recursive search, use \"*\". Eg.: \"/branches/*\""
9653 9697 msgstr ""
9654 9698
9655 9699 #: rhodecode/templates/base/vcs_settings.mako:294
9656 9700 msgid "Pull Request Settings"
9657 9701 msgstr ""
9658 9702
9659 9703 #: rhodecode/templates/base/vcs_settings.mako:299
9660 9704 msgid "Enable server-side merge for pull requests"
9661 9705 msgstr ""
9662 9706
9663 9707 #: rhodecode/templates/base/vcs_settings.mako:302
9664 9708 msgid "Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface."
9665 9709 msgstr ""
9666 9710
9667 9711 #: rhodecode/templates/base/vcs_settings.mako:306
9668 9712 msgid "Invalidate and relocate inline comments during update"
9669 9713 msgstr ""
9670 9714
9671 9715 #: rhodecode/templates/base/vcs_settings.mako:309
9672 9716 msgid "During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden."
9673 9717 msgstr ""
9674 9718
9675 9719 #: rhodecode/templates/base/vcs_settings.mako:318
9676 9720 msgid "Diff cache"
9677 9721 msgstr ""
9678 9722
9679 9723 #: rhodecode/templates/base/vcs_settings.mako:323
9680 9724 msgid "Enable caching diffs for pull requests cache and commits"
9681 9725 msgstr ""
9682 9726
9683 9727 #: rhodecode/templates/base/vcs_settings.mako:332
9684 9728 msgid "Mercurial Pull Request Settings"
9685 9729 msgstr ""
9686 9730
9687 9731 #: rhodecode/templates/base/vcs_settings.mako:338
9688 9732 msgid "Use rebase as merge strategy"
9689 9733 msgstr ""
9690 9734
9691 9735 #: rhodecode/templates/base/vcs_settings.mako:341
9692 9736 msgid "Use rebase instead of creating a merge commit when merging via web interface."
9693 9737 msgstr ""
9694 9738
9695 9739 #: rhodecode/templates/base/vcs_settings.mako:346
9696 9740 msgid "Close branch before merging it"
9697 9741 msgstr ""
9698 9742
9699 9743 #: rhodecode/templates/base/vcs_settings.mako:349
9700 9744 msgid "Close branch before merging it into destination branch. No effect when rebase strategy is use."
9701 9745 msgstr ""
9702 9746
9703 9747 #: rhodecode/templates/bookmarks/bookmarks.mako:5
9704 9748 #, python-format
9705 9749 msgid "%s Bookmarks"
9706 9750 msgstr ""
9707 9751
9708 9752 #: rhodecode/templates/bookmarks/bookmarks.mako:28
9709 9753 msgid "Compare Selected Bookmarks"
9710 9754 msgstr ""
9711 9755
9712 9756 #: rhodecode/templates/branches/branches.mako:5
9713 9757 #, python-format
9714 9758 msgid "%s Branches"
9715 9759 msgstr ""
9716 9760
9717 9761 #: rhodecode/templates/branches/branches.mako:28
9718 9762 msgid "Compare Selected Branches"
9719 9763 msgstr ""
9720 9764
9721 9765 #: rhodecode/templates/changeset/changeset.mako:11
9722 9766 msgid "{} Commit"
9723 9767 msgstr ""
9724 9768
9725 9769 #: rhodecode/templates/changeset/changeset.mako:78
9726 9770 #: rhodecode/templates/commits/changelog_elements.mako:56
9727 9771 #: rhodecode/templates/files/files_source_header.mako:48
9728 9772 #: rhodecode/templates/files/files_tree_header.mako:35
9729 9773 #: rhodecode/templates/summary/summary_commits.mako:43
9730 9774 msgid "Copy the full commit id"
9731 9775 msgstr ""
9732 9776
9733 9777 #: rhodecode/templates/changeset/changeset.mako:83
9734 9778 msgid "Commit phase"
9735 9779 msgstr ""
9736 9780
9737 9781 #: rhodecode/templates/changeset/changeset.mako:90
9738 9782 #: rhodecode/templates/changeset/changeset.mako:97
9739 9783 msgid "Evolve State"
9740 9784 msgstr ""
9741 9785
9742 9786 #: rhodecode/templates/changeset/changeset.mako:91
9743 9787 msgid "obsolete"
9744 9788 msgstr ""
9745 9789
9746 9790 #: rhodecode/templates/changeset/changeset.mako:98
9747 9791 msgid "hidden"
9748 9792 msgstr ""
9749 9793
9750 9794 #: rhodecode/templates/changeset/changeset.mako:104
9751 9795 msgid "Parent Commit"
9752 9796 msgstr ""
9753 9797
9754 9798 #: rhodecode/templates/changeset/changeset.mako:104
9755 9799 msgid "parent"
9756 9800 msgstr ""
9757 9801
9758 9802 #: rhodecode/templates/changeset/changeset.mako:108
9759 9803 msgid "Child Commit"
9760 9804 msgstr ""
9761 9805
9762 9806 #: rhodecode/templates/changeset/changeset.mako:108
9763 9807 msgid "child"
9764 9808 msgstr ""
9765 9809
9766 9810 #: rhodecode/templates/changeset/changeset.mako:118
9767 9811 msgid "Diff options"
9768 9812 msgstr ""
9769 9813
9770 9814 #: rhodecode/templates/changeset/changeset.mako:122
9771 9815 msgid "Raw Diff"
9772 9816 msgstr ""
9773 9817
9774 9818 #: rhodecode/templates/changeset/changeset.mako:126
9775 9819 msgid "Patch Diff"
9776 9820 msgstr ""
9777 9821
9778 9822 #: rhodecode/templates/changeset/changeset.mako:130
9779 9823 msgid "Download Diff"
9780 9824 msgstr ""
9781 9825
9782 9826 #: rhodecode/templates/changeset/changeset.mako:160
9783 9827 #: rhodecode/templates/pullrequests/pullrequest_show.mako:502
9784 9828 msgid "General Comments"
9785 9829 msgstr ""
9786 9830
9787 9831 #: rhodecode/templates/changeset/changeset.mako:201
9788 #: rhodecode/templates/pullrequests/pullrequest_show.mako:619
9832 #: rhodecode/templates/pullrequests/pullrequest_show.mako:605
9789 9833 msgid "Reviewers"
9790 9834 msgstr ""
9791 9835
9792 9836 #: rhodecode/templates/changeset/changeset.mako:242
9793 9837 #: rhodecode/templates/pullrequests/pullrequest_show.mako:579
9794 #: rhodecode/templates/pullrequests/pullrequest_show.mako:752
9838 #: rhodecode/templates/pullrequests/pullrequest_show.mako:748
9795 9839 msgid "No TODOs yet"
9796 9840 msgstr ""
9797 9841
9798 9842 #: rhodecode/templates/changeset/changeset.mako:274
9799 #: rhodecode/templates/pullrequests/pullrequest_show.mako:803
9843 #: rhodecode/templates/pullrequests/pullrequest_show.mako:799
9800 9844 msgid "No Comments yet"
9801 9845 msgstr ""
9802 9846
9803 9847 #: rhodecode/templates/changeset/changeset.mako:330
9804 9848 msgid "No Child Commits"
9805 9849 msgstr ""
9806 9850
9807 9851 #: rhodecode/templates/changeset/changeset.mako:377
9808 9852 msgid "No Parent Commits"
9809 9853 msgstr ""
9810 9854
9811 9855 #: rhodecode/templates/changeset/changeset_file_comment.mako:51
9812 9856 msgid "Draft comments are only visible to the author until submitted"
9813 9857 msgstr ""
9814 9858
9815 9859 #: rhodecode/templates/changeset/changeset_file_comment.mako:55
9816 9860 msgid "This comment was added while you browsed this page"
9817 9861 msgstr ""
9818 9862
9819 9863 #: rhodecode/templates/changeset/changeset_file_comment.mako:65
9820 9864 msgid "Resolved by comment #{}"
9821 9865 msgstr ""
9822 9866
9823 9867 #: rhodecode/templates/changeset/changeset_file_comment.mako:73
9824 9868 msgid "Click to create resolution comment."
9825 9869 msgstr ""
9826 9870
9827 9871 #: rhodecode/templates/changeset/changeset_file_comment.mako:82
9828 9872 msgid "This comment resolves TODO #{}"
9829 9873 msgstr ""
9830 9874
9831 9875 #: rhodecode/templates/changeset/changeset_file_comment.mako:114
9832 9876 msgid "Status from pull request."
9833 9877 msgstr ""
9834 9878
9835 9879 #: rhodecode/templates/changeset/changeset_file_comment.mako:133
9836 9880 msgid "Pull request author"
9837 9881 msgstr ""
9838 9882
9839 9883 #: rhodecode/templates/changeset/changeset_file_comment.mako:134
9840 9884 msgid "author"
9841 9885 msgstr ""
9842 9886
9843 9887 #: rhodecode/templates/changeset/changeset_file_comment.mako:187
9844 9888 #: rhodecode/templates/changeset/changeset_file_comment.mako:201
9845 9889 msgid "Outdated comment from pull request version v{0}, latest v{1}"
9846 9890 msgstr ""
9847 9891
9848 9892 #: rhodecode/templates/changeset/changeset_file_comment.mako:190
9849 9893 #: rhodecode/templates/changeset/changeset_file_comment.mako:206
9850 9894 msgid "Comment from pull request version v{0}, latest v{1}"
9851 9895 msgstr ""
9852 9896
9853 9897 #: rhodecode/templates/changeset/changeset_file_comment.mako:222
9854 9898 #: rhodecode/templates/compare/compare_diff.mako:108
9855 9899 #: rhodecode/templates/compare/compare_diff.mako:116
9856 9900 #: rhodecode/templates/compare/compare_diff.mako:124
9857 9901 #: rhodecode/templates/compare/compare_diff.mako:126
9858 9902 msgid "Comment"
9859 9903 msgstr ""
9860 9904
9861 9905 #: rhodecode/templates/changeset/changeset_file_comment.mako:223
9862 9906 #: rhodecode/templates/files/files_source.mako:117
9863 9907 msgid "Copy permalink"
9864 9908 msgstr ""
9865 9909
9866 9910 #: rhodecode/templates/changeset/changeset_file_comment.mako:241
9867 9911 msgid "Submit draft"
9868 9912 msgstr ""
9869 9913
9870 9914 #: rhodecode/templates/changeset/changeset_file_comment.mako:247
9871 9915 #: rhodecode/templates/changeset/changeset_file_comment.mako:250
9872 9916 #: rhodecode/templates/changeset/changeset_file_comment.mako:256
9873 9917 #: rhodecode/templates/changeset/changeset_file_comment.mako:259
9874 9918 msgid "Action unavailable"
9875 9919 msgstr ""
9876 9920
9877 9921 #: rhodecode/templates/changeset/changeset_file_comment.mako:267
9878 9922 msgid "Jump to the previous outdated comment"
9879 9923 msgstr ""
9880 9924
9881 9925 #: rhodecode/templates/changeset/changeset_file_comment.mako:268
9882 9926 msgid "Jump to the next outdated comment"
9883 9927 msgstr ""
9884 9928
9885 9929 #: rhodecode/templates/changeset/changeset_file_comment.mako:270
9886 9930 msgid "Jump to the previous comment"
9887 9931 msgstr ""
9888 9932
9889 9933 #: rhodecode/templates/changeset/changeset_file_comment.mako:271
9890 9934 msgid "Jump to the next comment"
9891 9935 msgstr ""
9892 9936
9893 9937 #: rhodecode/templates/changeset/changeset_file_comment.mako:311
9894 9938 msgid "Leave a comment on this Pull Request."
9895 9939 msgstr ""
9896 9940
9897 9941 #: rhodecode/templates/changeset/changeset_file_comment.mako:313
9898 9942 msgid "Leave a comment on {} commits in this range."
9899 9943 msgstr ""
9900 9944
9901 9945 #: rhodecode/templates/changeset/changeset_file_comment.mako:315
9902 9946 msgid "Leave a comment on this Commit."
9903 9947 msgstr ""
9904 9948
9905 9949 #: rhodecode/templates/changeset/changeset_file_comment.mako:403
9906 9950 msgid "You need to be logged in to leave comments."
9907 9951 msgstr ""
9908 9952
9909 9953 #: rhodecode/templates/changeset/changeset_file_comment.mako:404
9910 9954 msgid "Login now"
9911 9955 msgstr ""
9912 9956
9913 9957 #: rhodecode/templates/changeset/changeset_file_comment.mako:451
9914 9958 msgid "Mark as"
9915 9959 msgstr ""
9916 9960
9917 9961 #: rhodecode/templates/changeset/changeset_file_comment.mako:474
9918 9962 #: rhodecode/templates/files/files_upload.mako:86
9919 9963 msgid "Drag'n Drop files here or"
9920 9964 msgstr ""
9921 9965
9922 9966 #: rhodecode/templates/changeset/changeset_file_comment.mako:474
9923 9967 #: rhodecode/templates/files/files_upload.mako:86
9924 9968 msgid "Choose your files"
9925 9969 msgstr ""
9926 9970
9927 9971 #: rhodecode/templates/changeset/changeset_file_comment.mako:477
9928 9972 msgid "uploading..."
9929 9973 msgstr ""
9930 9974
9931 9975 #: rhodecode/templates/changeset/changeset_file_comment.mako:508
9932 9976 msgid "Add comment"
9933 9977 msgstr ""
9934 9978
9935 9979 #: rhodecode/templates/changeset/changeset_file_comment.mako:513
9936 9980 #: rhodecode/templates/changeset/changeset_file_comment.mako:515
9937 9981 msgid "Add draft"
9938 9982 msgstr ""
9939 9983
9940 9984 #: rhodecode/templates/changeset/changeset_file_comment.mako:526
9941 9985 #: rhodecode/templates/pullrequests/pullrequest_show.mako:46
9942 9986 #: rhodecode/templates/pullrequests/pullrequests.mako:31
9943 9987 msgid "Closed"
9944 9988 msgstr ""
9945 9989
9946 9990 #: rhodecode/templates/changeset/changeset_file_comment.mako:545
9947 9991 msgid "{} is supported."
9948 9992 msgstr ""
9949 9993
9950 9994 #: rhodecode/templates/changeset/changeset_range.mako:5
9951 9995 #, python-format
9952 9996 msgid "%s Commits"
9953 9997 msgstr ""
9954 9998
9955 9999 #: rhodecode/templates/changeset/changeset_range.mako:33
9956 10000 msgid "Commit Range"
9957 10001 msgstr ""
9958 10002
9959 10003 #: rhodecode/templates/changeset/changeset_range.mako:42
9960 10004 msgid "Range"
9961 10005 msgstr ""
9962 10006
9963 10007 #: rhodecode/templates/changeset/changeset_range.mako:60
9964 10008 msgid "Diff Option"
9965 10009 msgstr ""
9966 10010
9967 10011 #: rhodecode/templates/changeset/changeset_range.mako:71
9968 10012 #: rhodecode/templates/commits/changelog.mako:124
9969 10013 msgid "Show combined diff"
9970 10014 msgstr ""
9971 10015
9972 10016 #: rhodecode/templates/changeset/diff_block.mako:7
9973 10017 msgid "The requested commit is too big and content was truncated."
9974 10018 msgstr ""
9975 10019
9976 10020 #: rhodecode/templates/changeset/diff_block.mako:7
9977 10021 #: rhodecode/templates/changeset/diff_block.mako:10
9978 10022 #: rhodecode/templates/changeset/diff_block.mako:25
9979 10023 #: rhodecode/templates/changeset/diff_block.mako:46
9980 10024 #: rhodecode/templates/codeblocks/diffs.mako:196
9981 10025 #: rhodecode/templates/codeblocks/diffs.mako:327
9982 10026 msgid "Showing a big diff might take some time and resources, continue?"
9983 10027 msgstr ""
9984 10028
9985 10029 #: rhodecode/templates/changeset/diff_block.mako:7
9986 10030 #: rhodecode/templates/changeset/diff_block.mako:10
9987 10031 #: rhodecode/templates/changeset/diff_block.mako:25
9988 10032 #: rhodecode/templates/changeset/diff_block.mako:46
9989 10033 #: rhodecode/templates/codeblocks/diffs.mako:196
9990 10034 #: rhodecode/templates/codeblocks/diffs.mako:327
9991 10035 msgid "Show full diff"
9992 10036 msgstr ""
9993 10037
9994 10038 #: rhodecode/templates/changeset/diff_block.mako:10
9995 10039 msgid "The requested file is too big and its content is not shown."
9996 10040 msgstr ""
9997 10041
9998 10042 #: rhodecode/templates/changeset/diff_block.mako:25
9999 10043 #: rhodecode/templates/changeset/diff_block.mako:46
10000 10044 msgid "Diff was truncated. File content available only in full diff."
10001 10045 msgstr ""
10002 10046
10003 10047 #: rhodecode/templates/codeblocks/diffs.mako:129
10004 10048 msgid "not available in this view"
10005 10049 msgstr ""
10006 10050
10007 10051 #: rhodecode/templates/codeblocks/diffs.mako:138
10008 10052 msgid "{} unresolved"
10009 10053 msgstr ""
10010 10054
10011 10055 #: rhodecode/templates/codeblocks/diffs.mako:141
10012 10056 msgid "0 unresolved"
10013 10057 msgstr ""
10014 10058
10015 10059 #: rhodecode/templates/codeblocks/diffs.mako:144
10016 10060 msgid "{} Resolved"
10017 10061 msgstr ""
10018 10062
10019 10063 #: rhodecode/templates/codeblocks/diffs.mako:195
10020 10064 msgid "The requested changes are too big and content was truncated."
10021 10065 msgstr ""
10022 10066
10023 10067 #: rhodecode/templates/codeblocks/diffs.mako:212
10024 10068 msgid "Some changes may be hidden"
10025 10069 msgstr ""
10026 10070
10027 10071 #: rhodecode/templates/codeblocks/diffs.mako:214
10028 10072 msgid "No files"
10029 10073 msgstr ""
10030 10074
10031 10075 #: rhodecode/templates/codeblocks/diffs.mako:327
10032 10076 msgid "The requested commit or file is too big and content was truncated."
10033 10077 msgstr ""
10034 10078
10035 10079 #: rhodecode/templates/codeblocks/diffs.mako:334
10036 10080 #, python-format
10037 10081 msgid "This diff has been collapsed as it changes many lines, (%i lines changed)"
10038 10082 msgstr ""
10039 10083
10040 10084 #: rhodecode/templates/codeblocks/diffs.mako:336
10041 10085 msgid "Show them"
10042 10086 msgstr ""
10043 10087
10044 10088 #: rhodecode/templates/codeblocks/diffs.mako:339
10045 10089 msgid "Hide them"
10046 10090 msgstr ""
10047 10091
10048 10092 #: rhodecode/templates/codeblocks/diffs.mako:376
10049 10093 #: rhodecode/templates/codeblocks/diffs.mako:395
10050 10094 msgid "Unmatched/outdated inline comments below"
10051 10095 msgstr ""
10052 10096
10053 10097 #: rhodecode/templates/codeblocks/diffs.mako:401
10054 10098 msgid "Unmatched/outdated comments below"
10055 10099 msgstr ""
10056 10100
10057 10101 #: rhodecode/templates/codeblocks/diffs.mako:475
10058 10102 msgid "This file was removed from diff during updates to this pull-request."
10059 10103 msgstr ""
10060 10104
10061 10105 #: rhodecode/templates/codeblocks/diffs.mako:476
10062 10106 msgid "There are still outdated/unresolved comments attached to it."
10063 10107 msgstr ""
10064 10108
10065 10109 #: rhodecode/templates/codeblocks/diffs.mako:582
10066 10110 #: rhodecode/templates/codeblocks/diffs.mako:600
10067 10111 #, python-format
10068 10112 msgid "Show file at commit: %(commit_id)s"
10069 10113 msgstr ""
10070 10114
10071 10115 #: rhodecode/templates/codeblocks/diffs.mako:584
10072 10116 #: rhodecode/templates/codeblocks/diffs.mako:591
10073 10117 msgid "Show file before"
10074 10118 msgstr ""
10075 10119
10076 10120 #: rhodecode/templates/codeblocks/diffs.mako:589
10077 10121 #: rhodecode/templates/codeblocks/diffs.mako:607
10078 10122 #, python-format
10079 10123 msgid "File not present at commit: %(commit_id)s"
10080 10124 msgstr ""
10081 10125
10082 10126 #: rhodecode/templates/codeblocks/diffs.mako:602
10083 10127 #: rhodecode/templates/codeblocks/diffs.mako:609
10084 10128 msgid "Show file after"
10085 10129 msgstr ""
10086 10130
10087 10131 #: rhodecode/templates/codeblocks/diffs.mako:616
10088 10132 #: rhodecode/templates/codeblocks/diffs.mako:618
10089 10133 msgid "Hide comments"
10090 10134 msgstr ""
10091 10135
10092 10136 #: rhodecode/templates/codeblocks/diffs.mako:617
10093 10137 msgid "Show comments"
10094 10138 msgstr ""
10095 10139
10096 #: rhodecode/templates/codeblocks/diffs.mako:726
10097 #: rhodecode/templates/codeblocks/diffs.mako:773
10098 #: rhodecode/templates/codeblocks/diffs.mako:840
10140 #: rhodecode/templates/codeblocks/diffs.mako:732
10141 #: rhodecode/templates/codeblocks/diffs.mako:779
10142 #: rhodecode/templates/codeblocks/diffs.mako:846
10099 10143 msgid "Comments including outdated: {}. Click here to toggle them."
10100 10144 msgstr ""
10101 10145
10102 #: rhodecode/templates/codeblocks/diffs.mako:728
10103 #: rhodecode/templates/codeblocks/diffs.mako:775
10104 #: rhodecode/templates/codeblocks/diffs.mako:842
10146 #: rhodecode/templates/codeblocks/diffs.mako:734
10147 #: rhodecode/templates/codeblocks/diffs.mako:781
10148 #: rhodecode/templates/codeblocks/diffs.mako:848
10105 10149 msgid "Comments: {}. Click to toggle them."
10106 10150 msgstr ""
10107 10151
10108 #: rhodecode/templates/codeblocks/diffs.mako:914
10109 msgid "Toggle wide diff"
10110 msgstr ""
10111
10112 #: rhodecode/templates/codeblocks/diffs.mako:922
10113 msgid "View diff as side by side"
10152 #: rhodecode/templates/codeblocks/diffs.mako:921
10153 msgid "Scroll to page bottom"
10114 10154 msgstr ""
10115 10155
10116 10156 #: rhodecode/templates/codeblocks/diffs.mako:924
10117 msgid "Side by Side"
10118 msgstr ""
10119
10120 #: rhodecode/templates/codeblocks/diffs.mako:929
10121 msgid "View diff as unified"
10157 msgid "Scroll to page top"
10122 10158 msgstr ""
10123 10159
10124 10160 #: rhodecode/templates/codeblocks/diffs.mako:930
10125 msgid "Unified"
10126 msgstr ""
10127
10128 #: rhodecode/templates/codeblocks/diffs.mako:935
10129 msgid "Turn off: Show the diff as commit range"
10161 msgid "Toggle wide diff"
10130 10162 msgstr ""
10131 10163
10132 10164 #: rhodecode/templates/codeblocks/diffs.mako:938
10165 msgid "View diff as side by side"
10166 msgstr ""
10167
10168 #: rhodecode/templates/codeblocks/diffs.mako:940
10169 msgid "Side by Side"
10170 msgstr ""
10171
10133 10172 #: rhodecode/templates/codeblocks/diffs.mako:945
10173 msgid "View diff as unified"
10174 msgstr ""
10175
10176 #: rhodecode/templates/codeblocks/diffs.mako:946
10177 msgid "Unified"
10178 msgstr ""
10179
10180 #: rhodecode/templates/codeblocks/diffs.mako:951
10181 msgid "Turn off: Show the diff as commit range"
10182 msgstr ""
10183
10184 #: rhodecode/templates/codeblocks/diffs.mako:954
10185 #: rhodecode/templates/codeblocks/diffs.mako:961
10134 10186 msgid "Range Diff"
10135 10187 msgstr ""
10136 10188
10137 #: rhodecode/templates/codeblocks/diffs.mako:942
10189 #: rhodecode/templates/codeblocks/diffs.mako:958
10138 10190 msgid "Show the diff as commit range"
10139 10191 msgstr ""
10140 10192
10141 #: rhodecode/templates/codeblocks/diffs.mako:1007
10193 #: rhodecode/templates/codeblocks/diffs.mako:1050
10142 10194 msgid "Disabled on range diff"
10143 10195 msgstr ""
10144 10196
10145 #: rhodecode/templates/codeblocks/diffs.mako:1314
10146 msgid "..."
10147 msgstr ""
10148
10149 10197 #: rhodecode/templates/codeblocks/source.mako:22
10150 10198 msgid "view annotation from before this change"
10151 10199 msgstr ""
10152 10200
10153 10201 #: rhodecode/templates/commits/changelog.mako:6
10154 10202 #, python-format
10155 10203 msgid "%s Changelog"
10156 10204 msgstr ""
10157 10205
10158 10206 #: rhodecode/templates/commits/changelog.mako:38
10159 10207 msgid "Clear filter"
10160 10208 msgstr ""
10161 10209
10162 10210 #: rhodecode/templates/commits/changelog.mako:45
10163 10211 msgid "Hide obsolete/hidden"
10164 10212 msgstr ""
10165 10213
10166 10214 #: rhodecode/templates/commits/changelog.mako:47
10167 10215 msgid "Show obsolete/hidden"
10168 10216 msgstr ""
10169 10217
10170 10218 #: rhodecode/templates/commits/changelog.mako:50
10171 10219 msgid "Show hidden"
10172 10220 msgstr ""
10173 10221
10174 10222 #: rhodecode/templates/commits/changelog.mako:59
10175 10223 #: rhodecode/templates/compare/compare_diff.mako:93
10176 10224 #, python-format
10177 10225 msgid "Compare fork with %s"
10178 10226 msgstr ""
10179 10227
10180 10228 #: rhodecode/templates/commits/changelog.mako:69
10181 10229 #, python-format
10182 10230 msgid "Compare fork with Parent (%s)"
10183 10231 msgstr ""
10184 10232
10185 10233 #: rhodecode/templates/commits/changelog.mako:107
10186 10234 msgid "Clear selection"
10187 10235 msgstr ""
10188 10236
10189 10237 #: rhodecode/templates/commits/changelog.mako:110
10190 10238 msgid "Select second commit"
10191 10239 msgstr ""
10192 10240
10193 10241 #: rhodecode/templates/commits/changelog.mako:135
10194 10242 msgid "Commit Message"
10195 10243 msgstr ""
10196 10244
10197 10245 #: rhodecode/templates/commits/changelog.mako:137
10198 10246 #: rhodecode/templates/summary/summary_commits.mako:10
10199 10247 msgid "Age"
10200 10248 msgstr ""
10201 10249
10202 10250 #: rhodecode/templates/commits/changelog.mako:140
10203 10251 #: rhodecode/templates/summary/summary_commits.mako:12
10204 10252 msgid "Refs"
10205 10253 msgstr ""
10206 10254
10207 10255 #: rhodecode/templates/commits/changelog.mako:289
10208 10256 msgid "Branch filter"
10209 10257 msgstr ""
10210 10258
10211 10259 #: rhodecode/templates/commits/changelog.mako:350
10212 10260 msgid "There are no changes yet"
10213 10261 msgstr ""
10214 10262
10215 10263 #: rhodecode/templates/commits/changelog_elements.mako:8
10216 10264 msgid "load previous"
10217 10265 msgstr ""
10218 10266
10219 10267 #: rhodecode/templates/commits/changelog_elements.mako:35
10220 10268 #: rhodecode/templates/summary/summary_commits.mako:27
10221 10269 #, python-format
10222 10270 msgid ""
10223 10271 "Commit status: %s\n"
10224 10272 "Click to open associated pull request #%s"
10225 10273 msgstr ""
10226 10274
10227 10275 #: rhodecode/templates/commits/changelog_elements.mako:39
10228 10276 #: rhodecode/templates/summary/summary_commits.mako:31
10229 10277 msgid "Commit status: {}"
10230 10278 msgstr ""
10231 10279
10232 10280 #: rhodecode/templates/commits/changelog_elements.mako:45
10233 10281 #: rhodecode/templates/summary/summary_commits.mako:37
10234 10282 msgid "Commit status: Not Reviewed"
10235 10283 msgstr ""
10236 10284
10237 10285 #: rhodecode/templates/commits/changelog_elements.mako:63
10238 10286 msgid "{} commit phase"
10239 10287 msgstr ""
10240 10288
10241 10289 #: rhodecode/templates/commits/changelog_elements.mako:69
10242 10290 msgid "Obsolete Evolve State"
10243 10291 msgstr ""
10244 10292
10245 10293 #: rhodecode/templates/commits/changelog_elements.mako:74
10246 10294 msgid "Hidden Evolve State"
10247 10295 msgstr ""
10248 10296
10249 10297 #: rhodecode/templates/commits/changelog_elements.mako:80
10250 #: rhodecode/templates/compare/compare_commits.mako:47
10298 #: rhodecode/templates/compare/compare_commits.mako:55
10251 10299 #: rhodecode/templates/pullrequests/pullrequest_show.mako:433
10252 10300 #: rhodecode/templates/search/search_commit.mako:34
10253 10301 msgid "Expand commit message"
10254 10302 msgstr ""
10255 10303
10256 10304 #: rhodecode/templates/commits/changelog_elements.mako:108
10257 10305 #: rhodecode/templates/files/base.mako:51
10258 10306 #: rhodecode/templates/summary/summary_commits.mako:78
10259 10307 #, python-format
10260 10308 msgid "Branch %s"
10261 10309 msgstr ""
10262 10310
10263 10311 #: rhodecode/templates/commits/changelog_elements.mako:116
10264 10312 #: rhodecode/templates/files/base.mako:32
10265 10313 #: rhodecode/templates/summary/summary_commits.mako:64
10266 10314 #, python-format
10267 10315 msgid "Bookmark %s"
10268 10316 msgstr ""
10269 10317
10270 10318 #: rhodecode/templates/commits/changelog_elements.mako:124
10271 10319 #: rhodecode/templates/files/base.mako:42
10272 10320 #: rhodecode/templates/summary/summary_commits.mako:71
10273 10321 #, python-format
10274 10322 msgid "Tag %s"
10275 10323 msgstr ""
10276 10324
10277 10325 #: rhodecode/templates/commits/changelog_elements.mako:135
10278 10326 #: rhodecode/templates/summary/summary_commits.mako:87
10279 10327 msgid "Commit has comments"
10280 10328 msgstr ""
10281 10329
10282 10330 #: rhodecode/templates/commits/changelog_elements.mako:150
10283 10331 msgid "load next"
10284 10332 msgstr ""
10285 10333
10286 10334 #: rhodecode/templates/commits/changelog_file_history.mako:33
10287 10335 msgid "Show File"
10288 10336 msgstr ""
10289 10337
10290 10338 #: rhodecode/templates/commits/changelog_file_history.mako:38
10291 10339 msgid "Diff File"
10292 10340 msgstr ""
10293 10341
10294 10342 #: rhodecode/templates/commits/changelog_file_history.mako:46
10295 10343 msgid "Show Full History"
10296 10344 msgstr ""
10297 10345
10298 10346 #: rhodecode/templates/compare/compare_commits.mako:5
10299 10347 msgid "Compare was calculated based on this common ancestor commit"
10300 10348 msgstr ""
10301 10349
10302 #: rhodecode/templates/compare/compare_commits.mako:15
10350 #: rhodecode/templates/compare/compare_commits.mako:18
10303 10351 #: rhodecode/templates/pullrequests/pullrequest_show.mako:394
10304 10352 msgid "Time"
10305 10353 msgstr ""
10306 10354
10307 #: rhodecode/templates/compare/compare_commits.mako:65
10355 #: rhodecode/templates/compare/compare_commits.mako:37
10356 msgid "Pull request version this commit was introduced"
10357 msgstr ""
10358
10359 #: rhodecode/templates/compare/compare_commits.mako:73
10308 10360 msgid "No commits in this compare"
10309 10361 msgstr ""
10310 10362
10311 10363 #: rhodecode/templates/compare/compare_diff.mako:7
10312 10364 #: rhodecode/templates/compare/compare_diff.mako:9
10313 10365 #, python-format
10314 10366 msgid "%s Compare"
10315 10367 msgstr ""
10316 10368
10317 10369 #: rhodecode/templates/compare/compare_diff.mako:38
10318 10370 #: rhodecode/templates/compare/compare_diff.mako:90
10319 10371 #: rhodecode/templates/compare/compare_diff.mako:114
10320 10372 #: rhodecode/templates/compare/compare_diff.mako:119
10321 10373 msgid "Compare Commits"
10322 10374 msgstr ""
10323 10375
10324 10376 #: rhodecode/templates/compare/compare_diff.mako:40
10325 10377 msgid "for file"
10326 10378 msgstr ""
10327 10379
10328 10380 #: rhodecode/templates/compare/compare_diff.mako:56
10329 10381 msgid "Target"
10330 10382 msgstr ""
10331 10383
10332 10384 #: rhodecode/templates/compare/compare_diff.mako:70
10333 10385 #: rhodecode/templates/files/files_source.mako:110
10334 10386 msgid "Source"
10335 10387 msgstr ""
10336 10388
10337 10389 #: rhodecode/templates/compare/compare_diff.mako:102
10338 10390 msgid "Compare with origin"
10339 10391 msgstr ""
10340 10392
10341 10393 #: rhodecode/templates/compare/compare_diff.mako:107
10342 10394 #: rhodecode/templates/compare/compare_diff.mako:108
10343 10395 #: rhodecode/templates/compare/compare_diff.mako:114
10344 10396 #: rhodecode/templates/compare/compare_diff.mako:115
10345 10397 #: rhodecode/templates/compare/compare_diff.mako:116
10346 10398 #: rhodecode/templates/compare/compare_diff.mako:126
10347 10399 msgid "Action unavailable in current view"
10348 10400 msgstr ""
10349 10401
10350 10402 #: rhodecode/templates/compare/compare_diff.mako:107
10351 10403 #: rhodecode/templates/compare/compare_diff.mako:115
10352 10404 #: rhodecode/templates/compare/compare_diff.mako:120
10353 10405 msgid "Swap"
10354 10406 msgstr ""
10355 10407
10356 10408 #: rhodecode/templates/compare/compare_diff.mako:110
10357 10409 msgid "Compare commits, branches, bookmarks or tags."
10358 10410 msgstr ""
10359 10411
10360 10412 #: rhodecode/templates/compare/compare_diff.mako:139
10361 10413 #: rhodecode/templates/email_templates/commit_comment.mako:57
10362 10414 msgid "Commit status"
10363 10415 msgstr ""
10364 10416
10365 10417 #: rhodecode/templates/data_table/_dt_elements.mako:73
10366 10418 #: rhodecode/templates/data_table/_dt_elements.mako:74
10367 10419 msgid "Fork"
10368 10420 msgstr ""
10369 10421
10370 10422 #: rhodecode/templates/data_table/_dt_elements.mako:95
10371 10423 msgid "Mercurial repository"
10372 10424 msgstr ""
10373 10425
10374 10426 #: rhodecode/templates/data_table/_dt_elements.mako:97
10375 10427 msgid "Git repository"
10376 10428 msgstr ""
10377 10429
10378 10430 #: rhodecode/templates/data_table/_dt_elements.mako:99
10379 10431 msgid "Subversion repository"
10380 10432 msgstr ""
10381 10433
10382 10434 #: rhodecode/templates/data_table/_dt_elements.mako:106
10383 10435 msgid "Public repository"
10384 10436 msgstr ""
10385 10437
10386 10438 #: rhodecode/templates/data_table/_dt_elements.mako:116
10387 10439 msgid "This repository is being created in a background task"
10388 10440 msgstr ""
10389 10441
10390 10442 #: rhodecode/templates/data_table/_dt_elements.mako:117
10391 10443 msgid "creating..."
10392 10444 msgstr ""
10393 10445
10394 10446 #: rhodecode/templates/data_table/_dt_elements.mako:149
10395 10447 msgid "No commits yet"
10396 10448 msgstr ""
10397 10449
10398 10450 #: rhodecode/templates/data_table/_dt_elements.mako:156
10399 10451 #: rhodecode/templates/data_table/_dt_elements.mako:158
10400 10452 #, python-format
10401 10453 msgid "Subscribe to %s rss feed"
10402 10454 msgstr ""
10403 10455
10404 10456 #: rhodecode/templates/data_table/_dt_elements.mako:164
10405 10457 #: rhodecode/templates/data_table/_dt_elements.mako:166
10406 10458 #, python-format
10407 10459 msgid "Subscribe to %s atom feed"
10408 10460 msgstr ""
10409 10461
10410 10462 #: rhodecode/templates/data_table/_dt_elements.mako:191
10411 10463 msgid "Creating"
10412 10464 msgstr ""
10413 10465
10414 10466 #: rhodecode/templates/data_table/_dt_elements.mako:193
10415 10467 #: rhodecode/templates/hovercards/hovercard_pull_request.mako:10
10416 10468 msgid "Created"
10417 10469 msgstr ""
10418 10470
10419 10471 #: rhodecode/templates/data_table/_dt_elements.mako:236
10420 10472 msgid "personal"
10421 10473 msgstr ""
10422 10474
10423 10475 #: rhodecode/templates/data_table/_dt_elements.mako:387
10424 10476 #: rhodecode/templates/pullrequests/pullrequest_show.mako:57
10425 10477 msgid "Pull request !{}"
10426 10478 msgstr ""
10427 10479
10428 10480 #: rhodecode/templates/data_table/_dt_elements.mako:396
10429 10481 msgid "Work in progress"
10430 10482 msgstr ""
10431 10483
10432 10484 #: rhodecode/templates/data_table/_dt_elements.mako:432
10433 10485 msgid "Info"
10434 10486 msgstr ""
10435 10487
10436 10488 #: rhodecode/templates/data_table/_dt_elements.mako:473
10437 10489 #, python-format
10438 10490 msgid "Parsed using %s syntax"
10439 10491 msgstr ""
10440 10492
10441 10493 #: rhodecode/templates/debug_style/buttons.html:131
10442 10494 msgid "Confirm to remove this field: Field"
10443 10495 msgstr ""
10444 10496
10445 10497 #: rhodecode/templates/debug_style/form-elements.html:107
10446 10498 msgid "Default"
10447 10499 msgstr ""
10448 10500
10449 10501 #: rhodecode/templates/debug_style/form-elements.html:513
10450 10502 #: rhodecode/templates/debug_style/form-elements.html:565
10451 10503 #: rhodecode/templates/debug_style/forms.html:236
10452 10504 msgid "Chosen group members"
10453 10505 msgstr ""
10454 10506
10455 10507 #: rhodecode/templates/debug_style/form-elements.html:525
10456 10508 #: rhodecode/templates/debug_style/form-elements.html:569
10457 10509 #: rhodecode/templates/debug_style/forms.html:240
10458 10510 msgid "Remove all elements"
10459 10511 msgstr ""
10460 10512
10461 10513 #: rhodecode/templates/debug_style/form-elements.html:535
10462 10514 #: rhodecode/templates/debug_style/form-elements.html:579
10463 10515 #: rhodecode/templates/debug_style/forms.html:250
10464 10516 msgid "Available members"
10465 10517 msgstr ""
10466 10518
10467 10519 #: rhodecode/templates/debug_style/form-elements.html:543
10468 10520 #: rhodecode/templates/debug_style/form-elements.html:587
10469 10521 #: rhodecode/templates/debug_style/forms.html:258
10470 10522 msgid "Add all elements"
10471 10523 msgstr ""
10472 10524
10473 10525 #: rhodecode/templates/debug_style/forms.html:119
10474 10526 msgid "Some text..."
10475 10527 msgstr ""
10476 10528
10477 10529 #: rhodecode/templates/debug_style/forms.html:122
10478 10530 #: rhodecode/templates/debug_style/forms.html:255
10479 10531 msgid "Variable Item"
10480 10532 msgstr ""
10481 10533
10482 10534 #: rhodecode/templates/debug_style/forms.html:252
10483 10535 msgid "Some example text..."
10484 10536 msgstr ""
10485 10537
10486 10538 #: rhodecode/templates/debug_style/index.html:5
10487 10539 msgid "Debug Style"
10488 10540 msgstr ""
10489 10541
10490 10542 #: rhodecode/templates/debug_style/index.html:54
10491 10543 msgid "Index"
10492 10544 msgstr ""
10493 10545
10494 10546 #: rhodecode/templates/debug_style/index.html:56
10495 10547 msgid "Typography"
10496 10548 msgstr ""
10497 10549
10498 10550 #: rhodecode/templates/debug_style/index.html:57
10499 10551 msgid "Forms"
10500 10552 msgstr ""
10501 10553
10502 10554 #: rhodecode/templates/debug_style/index.html:58
10503 10555 msgid "Buttons"
10504 10556 msgstr ""
10505 10557
10506 10558 #: rhodecode/templates/debug_style/index.html:59
10507 10559 msgid "Labels"
10508 10560 msgstr ""
10509 10561
10510 10562 #: rhodecode/templates/debug_style/index.html:60
10511 10563 msgid "Alerts"
10512 10564 msgstr ""
10513 10565
10514 10566 #: rhodecode/templates/debug_style/index.html:61
10515 10567 msgid "Tables"
10516 10568 msgstr ""
10517 10569
10518 10570 #: rhodecode/templates/debug_style/index.html:62
10519 10571 msgid "Tables wide"
10520 10572 msgstr ""
10521 10573
10522 10574 #: rhodecode/templates/debug_style/index.html:63
10523 10575 msgid "Collapsable Content"
10524 10576 msgstr ""
10525 10577
10526 10578 #: rhodecode/templates/debug_style/index.html:65
10527 10579 msgid "Layout form with sidebar"
10528 10580 msgstr ""
10529 10581
10530 10582 #: rhodecode/templates/debug_style/index.html:66
10531 10583 msgid "Login"
10532 10584 msgstr ""
10533 10585
10534 10586 #: rhodecode/templates/debug_style/index.html:67
10535 10587 msgid "Login 2"
10536 10588 msgstr ""
10537 10589
10538 10590 #: rhodecode/templates/debug_style/index.html:68
10539 10591 msgid "Code blocks"
10540 10592 msgstr ""
10541 10593
10542 10594 #: rhodecode/templates/debug_style/index.html:71
10543 10595 msgid "Panels"
10544 10596 msgstr ""
10545 10597
10546 10598 #: rhodecode/templates/debug_style/index.html:74
10547 10599 msgid "Form elements"
10548 10600 msgstr ""
10549 10601
10550 10602 #: rhodecode/templates/debug_style/index.html:75
10551 10603 msgid "Form elements small"
10552 10604 msgstr ""
10553 10605
10554 10606 #: rhodecode/templates/debug_style/index.html:76
10555 10607 msgid "Form inline elements"
10556 10608 msgstr ""
10557 10609
10558 10610 #: rhodecode/templates/debug_style/index.html:77
10559 10611 msgid "Form vertical"
10560 10612 msgstr ""
10561 10613
10562 10614 #: rhodecode/templates/debug_style/login.html:28
10563 10615 msgid "Sign in to your account"
10564 10616 msgstr ""
10565 10617
10566 10618 #: rhodecode/templates/debug_style/login.html:46
10567 10619 msgid "(Forgot password?)"
10568 10620 msgstr ""
10569 10621
10570 10622 #: rhodecode/templates/debug_style/login.html:56
10571 10623 msgid "Don't have an account ?"
10572 10624 msgstr ""
10573 10625
10574 10626 #: rhodecode/templates/email_templates/base.mako:72
10575 10627 #: rhodecode/templates/email_templates/base.mako:639
10576 10628 msgid "This is a notification from RhodeCode."
10577 10629 msgstr ""
10578 10630
10579 10631 #: rhodecode/templates/email_templates/base.mako:619
10580 10632 msgid "RhodeCode"
10581 10633 msgstr ""
10582 10634
10583 10635 #: rhodecode/templates/email_templates/commit_comment.mako:24
10584 10636 msgid "{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in commit `{commit_id}` in the `{repo_name}` repository"
10585 10637 msgstr ""
10586 10638
10587 10639 #: rhodecode/templates/email_templates/commit_comment.mako:28
10588 10640 msgid "{mention_prefix}[status: {status}] {user} left a {comment_type} on commit `{commit_id}` in the `{repo_name}` repository"
10589 10641 msgstr ""
10590 10642
10591 10643 #: rhodecode/templates/email_templates/commit_comment.mako:31
10592 10644 msgid "{mention_prefix}{user} left a {comment_type} on commit `{commit_id}` in the `{repo_name}` repository"
10593 10645 msgstr ""
10594 10646
10595 10647 #: rhodecode/templates/email_templates/commit_comment.mako:54
10596 10648 #: rhodecode/templates/email_templates/pull_request_comment.mako:63
10597 10649 msgid "Comment link"
10598 10650 msgstr ""
10599 10651
10600 10652 #: rhodecode/templates/email_templates/commit_comment.mako:57
10601 10653 #: rhodecode/templates/email_templates/commit_comment.mako:135
10602 10654 msgid "Status was changed to"
10603 10655 msgstr ""
10604 10656
10605 10657 #: rhodecode/templates/email_templates/commit_comment.mako:62
10606 10658 #: rhodecode/templates/email_templates/commit_comment.mako:147
10607 10659 #: rhodecode/templates/search/search_commit.mako:12
10608 10660 #: rhodecode/templates/summary/summary_commits.mako:9
10609 10661 msgid "Commit message"
10610 10662 msgstr ""
10611 10663
10612 10664 #: rhodecode/templates/email_templates/commit_comment.mako:65
10613 10665 #: rhodecode/templates/email_templates/pull_request_comment.mako:77
10614 10666 msgid "File: {comment_file} on line {comment_line}"
10615 10667 msgstr ""
10616 10668
10617 10669 #: rhodecode/templates/email_templates/commit_comment.mako:69
10618 10670 #: rhodecode/templates/email_templates/commit_comment.mako:161
10619 10671 #: rhodecode/templates/email_templates/pull_request_comment.mako:81
10620 10672 #: rhodecode/templates/email_templates/pull_request_comment.mako:191
10621 10673 msgid "`TODO` number"
10622 10674 msgstr ""
10623 10675
10624 10676 #: rhodecode/templates/email_templates/commit_comment.mako:71
10625 10677 #: rhodecode/templates/email_templates/commit_comment.mako:163
10626 10678 #: rhodecode/templates/email_templates/pull_request_comment.mako:83
10627 10679 #: rhodecode/templates/email_templates/pull_request_comment.mako:193
10628 10680 msgid "`Note` number"
10629 10681 msgstr ""
10630 10682
10631 10683 #: rhodecode/templates/email_templates/commit_comment.mako:104
10632 10684 #: rhodecode/templates/email_templates/pull_request_comment.mako:124
10633 10685 msgid "left a"
10634 10686 msgstr ""
10635 10687
10636 10688 #: rhodecode/templates/email_templates/commit_comment.mako:107
10637 10689 msgid "{comment_type} on file `{comment_file}` in commit."
10638 10690 msgstr ""
10639 10691
10640 10692 #: rhodecode/templates/email_templates/commit_comment.mako:109
10641 10693 msgid "{comment_type} on commit."
10642 10694 msgstr ""
10643 10695
10644 10696 #: rhodecode/templates/email_templates/commit_comment.mako:114
10645 10697 msgid "of repository"
10646 10698 msgstr ""
10647 10699
10648 10700 #: rhodecode/templates/email_templates/commit_comment.mako:133
10649 10701 msgid "Commit Status"
10650 10702 msgstr ""
10651 10703
10652 10704 #: rhodecode/templates/email_templates/commit_comment.mako:153
10653 10705 #: rhodecode/templates/email_templates/pull_request_comment.mako:183
10654 10706 #: rhodecode/templates/search/search_path.mako:9
10655 10707 msgid "File"
10656 10708 msgstr ""
10657 10709
10658 10710 #: rhodecode/templates/email_templates/commit_comment.mako:154
10659 10711 #: rhodecode/templates/email_templates/pull_request_comment.mako:184
10660 10712 msgid "`{comment_file}` on line {comment_line}"
10661 10713 msgstr ""
10662 10714
10663 10715 #: rhodecode/templates/email_templates/commit_comment.mako:173
10664 10716 #: rhodecode/templates/email_templates/pull_request_comment.mako:203
10665 10717 msgid "Reply"
10666 10718 msgstr ""
10667 10719
10668 10720 #: rhodecode/templates/email_templates/pull_request_comment.mako:24
10669 10721 msgid "{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: \"{pr_title}\""
10670 10722 msgstr ""
10671 10723
10672 10724 #: rhodecode/templates/email_templates/pull_request_comment.mako:28
10673 10725 msgid "{mention_prefix}[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: \"{pr_title}\""
10674 10726 msgstr ""
10675 10727
10676 10728 #: rhodecode/templates/email_templates/pull_request_comment.mako:31
10677 10729 msgid "{mention_prefix}{user} left a {comment_type} on pull request !{pr_id}: \"{pr_title}\""
10678 10730 msgstr ""
10679 10731
10680 10732 #: rhodecode/templates/email_templates/pull_request_comment.mako:65
10681 10733 #: rhodecode/templates/hovercards/hovercard_pull_request.mako:24
10682 10734 msgid "Pull Request"
10683 10735 msgstr ""
10684 10736
10685 10737 #: rhodecode/templates/email_templates/pull_request_comment.mako:67
10686 10738 #: rhodecode/templates/email_templates/pull_request_review.mako:45
10687 10739 #: rhodecode/templates/email_templates/pull_request_update.mako:41
10688 10740 msgid "Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}"
10689 10741 msgstr ""
10690 10742
10691 10743 #: rhodecode/templates/email_templates/pull_request_comment.mako:70
10692 10744 msgid "{user} submitted pull request !{pr_id} status: *{status}*"
10693 10745 msgstr ""
10694 10746
10695 10747 #: rhodecode/templates/email_templates/pull_request_comment.mako:73
10696 10748 msgid "{user} submitted pull request !{pr_id} status: *{status} and closed*"
10697 10749 msgstr ""
10698 10750
10699 10751 #: rhodecode/templates/email_templates/pull_request_comment.mako:127
10700 10752 msgid "{comment_type} on file `{comment_file}` in pull request."
10701 10753 msgstr ""
10702 10754
10703 10755 #: rhodecode/templates/email_templates/pull_request_comment.mako:129
10704 10756 msgid "{comment_type} on pull request."
10705 10757 msgstr ""
10706 10758
10707 10759 #: rhodecode/templates/email_templates/pull_request_comment.mako:134
10708 10760 #: rhodecode/templates/email_templates/pull_request_comment.mako:164
10709 10761 #: rhodecode/templates/email_templates/pull_request_review.mako:100
10710 10762 #: rhodecode/templates/email_templates/pull_request_review.mako:116
10711 10763 #: rhodecode/templates/email_templates/pull_request_update.mako:104
10712 10764 #: rhodecode/templates/email_templates/pull_request_update.mako:121
10713 10765 msgid "Pull request"
10714 10766 msgstr ""
10715 10767
10716 10768 #: rhodecode/templates/email_templates/pull_request_comment.mako:153
10717 10769 msgid "Review Status"
10718 10770 msgstr ""
10719 10771
10720 10772 #: rhodecode/templates/email_templates/pull_request_comment.mako:156
10721 10773 msgid "Closed pull request with status"
10722 10774 msgstr ""
10723 10775
10724 10776 #: rhodecode/templates/email_templates/pull_request_comment.mako:158
10725 10777 msgid "Submitted review status"
10726 10778 msgstr ""
10727 10779
10728 10780 #: rhodecode/templates/email_templates/pull_request_comment.mako:173
10729 10781 #: rhodecode/templates/email_templates/pull_request_review.mako:125
10730 10782 #: rhodecode/templates/email_templates/pull_request_update.mako:130
10731 10783 msgid "Commit Flow"
10732 10784 msgstr ""
10733 10785
10734 10786 #: rhodecode/templates/email_templates/pull_request_comment.mako:175
10735 10787 #: rhodecode/templates/email_templates/pull_request_comment.mako:177
10736 10788 #: rhodecode/templates/email_templates/pull_request_review.mako:127
10737 10789 #: rhodecode/templates/email_templates/pull_request_review.mako:129
10738 10790 #: rhodecode/templates/email_templates/pull_request_update.mako:132
10739 10791 #: rhodecode/templates/email_templates/pull_request_update.mako:134
10740 10792 #: rhodecode/templates/pullrequests/pullrequest_show.mako:112
10741 10793 #: rhodecode/templates/pullrequests/pullrequest_show.mako:121
10742 10794 msgid "of"
10743 10795 msgstr ""
10744 10796
10745 10797 #: rhodecode/templates/email_templates/pull_request_review.mako:15
10746 10798 msgid "{user} added you as observer to pull request. !{pr_id}: \"{pr_title}\""
10747 10799 msgstr ""
10748 10800
10749 10801 #: rhodecode/templates/email_templates/pull_request_review.mako:17
10750 10802 msgid "{user} requested a pull request review. !{pr_id}: \"{pr_title}\""
10751 10803 msgstr ""
10752 10804
10753 10805 #: rhodecode/templates/email_templates/pull_request_review.mako:43
10754 10806 #: rhodecode/templates/email_templates/pull_request_update.mako:39
10755 10807 msgid "Pull Request link"
10756 10808 msgstr ""
10757 10809
10758 10810 #: rhodecode/templates/email_templates/pull_request_review.mako:89
10759 10811 msgid "added you as observer to"
10760 10812 msgstr ""
10761 10813
10762 10814 #: rhodecode/templates/email_templates/pull_request_review.mako:95
10763 10815 msgid "requested a"
10764 10816 msgstr ""
10765 10817
10766 10818 #: rhodecode/templates/email_templates/pull_request_update.mako:14
10767 10819 msgid "{updating_user} updated pull request. !{pr_id}: \"{pr_title}\""
10768 10820 msgstr ""
10769 10821
10770 10822 #: rhodecode/templates/email_templates/pull_request_update.mako:98
10771 10823 msgid "updated"
10772 10824 msgstr ""
10773 10825
10774 10826 #: rhodecode/templates/email_templates/pull_request_update.mako:100
10775 10827 msgid "pull request."
10776 10828 msgstr ""
10777 10829
10778 10830 #: rhodecode/templates/email_templates/pull_request_update.mako:143
10779 10831 msgid "Changes"
10780 10832 msgstr ""
10781 10833
10782 10834 #: rhodecode/templates/email_templates/test.mako:5
10783 10835 msgid "hello \"world\""
10784 10836 msgstr ""
10785 10837
10786 10838 #: rhodecode/templates/email_templates/test.mako:18
10787 10839 msgid "Translation String"
10788 10840 msgstr ""
10789 10841
10790 10842 #: rhodecode/templates/email_templates/user_registration.mako:28
10791 10843 msgid "New user {user} has registered on {date}"
10792 10844 msgstr ""
10793 10845
10794 10846 #: rhodecode/templates/email_templates/user_registration.mako:47
10795 10847 msgid "Full Name"
10796 10848 msgstr ""
10797 10849
10798 10850 #: rhodecode/templates/errors/error_document.mako:48
10799 10851 #, python-format
10800 10852 msgid "You will be redirected to %s in %s seconds"
10801 10853 msgstr ""
10802 10854
10803 10855 #: rhodecode/templates/errors/error_document.mako:66
10804 10856 msgid "Support Page"
10805 10857 msgstr ""
10806 10858
10807 10859 #: rhodecode/templates/feed/atom_feed_entry.mako:3
10808 10860 #, python-format
10809 10861 msgid "%(user)s commited on %(date)s UTC"
10810 10862 msgstr ""
10811 10863
10812 10864 #: rhodecode/templates/feed/atom_feed_entry.mako:30
10813 10865 msgid "Commit was too big and was cut off..."
10814 10866 msgstr ""
10815 10867
10816 10868 #: rhodecode/templates/files/file_authors_box.mako:18
10817 10869 msgid "Load All Authors"
10818 10870 msgstr ""
10819 10871
10820 10872 #: rhodecode/templates/files/file_authors_box.mako:20
10821 10873 msgid "last author"
10822 10874 msgstr ""
10823 10875
10824 10876 #: rhodecode/templates/files/files.mako:4
10825 10877 #: rhodecode/templates/files/files_pjax.mako:2
10826 10878 msgid "{} Files"
10827 10879 msgstr ""
10828 10880
10829 10881 #: rhodecode/templates/files/files_add.mako:4
10830 10882 msgid "{} Files Add"
10831 10883 msgstr ""
10832 10884
10833 10885 #: rhodecode/templates/files/files_add.mako:25
10834 10886 msgid "Add new file"
10835 10887 msgstr ""
10836 10888
10837 10889 #: rhodecode/templates/files/files_add.mako:43
10838 10890 #: rhodecode/templates/files/files_edit.mako:44
10839 10891 msgid "Filename e.g example.py, or docs/readme.md"
10840 10892 msgstr ""
10841 10893
10842 10894 #: rhodecode/templates/files/files_add.mako:63
10843 10895 #: rhodecode/templates/files/files_edit.mako:65
10844 10896 msgid "Line wraps on"
10845 10897 msgstr ""
10846 10898
10847 10899 #: rhodecode/templates/files/files_add.mako:63
10848 10900 #: rhodecode/templates/files/files_edit.mako:65
10849 10901 msgid "line wraps off"
10850 10902 msgstr ""
10851 10903
10852 10904 #: rhodecode/templates/files/files_add.mako:86
10853 10905 #: rhodecode/templates/files/files_delete.mako:71
10854 10906 #: rhodecode/templates/files/files_edit.mako:88
10855 10907 #: rhodecode/templates/files/files_upload.mako:102
10856 10908 msgid "Commit changes"
10857 10909 msgstr ""
10858 10910
10859 10911 #: rhodecode/templates/files/files_browser.mako:9
10860 10912 msgid "Previous commit"
10861 10913 msgstr ""
10862 10914
10863 10915 #: rhodecode/templates/files/files_browser.mako:15
10864 10916 msgid "Next commit"
10865 10917 msgstr ""
10866 10918
10867 10919 #: rhodecode/templates/files/files_browser.mako:24
10868 10920 msgid "Add File"
10869 10921 msgstr ""
10870 10922
10871 10923 #: rhodecode/templates/files/files_browser.mako:27
10872 10924 msgid "more options"
10873 10925 msgstr ""
10874 10926
10875 10927 #: rhodecode/templates/files/files_browser.mako:36
10876 10928 msgid "Upload File"
10877 10929 msgstr ""
10878 10930
10879 10931 #: rhodecode/templates/files/files_browser.mako:50
10880 10932 msgid "Full tree as {}"
10881 10933 msgstr ""
10882 10934
10883 10935 #: rhodecode/templates/files/files_browser.mako:53
10884 10936 msgid "This tree as {}"
10885 10937 msgstr ""
10886 10938
10887 10939 #: rhodecode/templates/files/files_browser.mako:63
10888 10940 #: rhodecode/templates/summary/components.mako:196
10889 10941 msgid "more download options"
10890 10942 msgstr ""
10891 10943
10892 10944 #: rhodecode/templates/files/files_browser.mako:106
10893 10945 #: rhodecode/templates/summary/summary.mako:37
10894 10946 #, python-format
10895 10947 msgid "Readme file from commit %s:%s"
10896 10948 msgstr ""
10897 10949
10898 10950 #: rhodecode/templates/files/files_browser_tree.mako:17
10899 10951 #: rhodecode/templates/search/search_path.mako:12
10900 10952 msgid "Size"
10901 10953 msgstr ""
10902 10954
10903 10955 #: rhodecode/templates/files/files_browser_tree.mako:18
10904 10956 msgid "Modified"
10905 10957 msgstr ""
10906 10958
10907 10959 #: rhodecode/templates/files/files_browser_tree.mako:19
10908 10960 msgid "Last Commit"
10909 10961 msgstr ""
10910 10962
10911 10963 #: rhodecode/templates/files/files_delete.mako:4
10912 10964 msgid "{} Files Delete"
10913 10965 msgstr ""
10914 10966
10915 10967 #: rhodecode/templates/files/files_delete.mako:25
10916 10968 msgid "Delete file"
10917 10969 msgstr ""
10918 10970
10919 10971 #: rhodecode/templates/files/files_delete.mako:52
10920 10972 #, python-format
10921 10973 msgid "Binary file (%s)"
10922 10974 msgstr ""
10923 10975
10924 10976 #: rhodecode/templates/files/files_delete.mako:57
10925 10977 #: rhodecode/templates/files/files_source.mako:138
10926 10978 msgid "File size {} is bigger then allowed limit {}. "
10927 10979 msgstr ""
10928 10980
10929 10981 #: rhodecode/templates/files/files_edit.mako:4
10930 10982 msgid "{} Files Edit"
10931 10983 msgstr ""
10932 10984
10933 10985 #: rhodecode/templates/files/files_edit.mako:25
10934 10986 msgid "Edit file"
10935 10987 msgstr ""
10936 10988
10937 10989 #: rhodecode/templates/files/files_source.mako:26
10938 10990 msgid "Download largefile"
10939 10991 msgstr ""
10940 10992
10941 10993 #: rhodecode/templates/files/files_source.mako:30
10942 10994 msgid "Download file"
10943 10995 msgstr ""
10944 10996
10945 10997 #: rhodecode/templates/files/files_source.mako:39
10946 10998 msgid "Editing binary files not allowed"
10947 10999 msgstr ""
10948 11000
10949 11001 #: rhodecode/templates/files/files_source.mako:43
10950 11002 msgid "Edit on branch: "
10951 11003 msgstr ""
10952 11004
10953 11005 #: rhodecode/templates/files/files_source.mako:52
10954 11006 msgid "Editing files allowed only when on branch head commit"
10955 11007 msgstr ""
10956 11008
10957 11009 #: rhodecode/templates/files/files_source.mako:53
10958 11010 msgid "Deleting files allowed only when on branch head commit"
10959 11011 msgstr ""
10960 11012
10961 11013 #: rhodecode/templates/files/files_source.mako:75
10962 11014 msgid "lines"
10963 11015 msgstr ""
10964 11016
10965 11017 #: rhodecode/templates/files/files_source.mako:83
10966 11018 msgid "This file is a pointer to large binary file"
10967 11019 msgstr ""
10968 11020
10969 11021 #: rhodecode/templates/files/files_source.mako:83
10970 11022 msgid "LargeFile"
10971 11023 msgstr ""
10972 11024
10973 11025 #: rhodecode/templates/files/files_source.mako:106
10974 11026 msgid "History"
10975 11027 msgstr ""
10976 11028
10977 11029 #: rhodecode/templates/files/files_source.mako:112
10978 11030 msgid "Annotation"
10979 11031 msgstr ""
10980 11032
10981 11033 #: rhodecode/templates/files/files_source.mako:114
10982 11034 msgid "Raw"
10983 11035 msgstr ""
10984 11036
10985 11037 #: rhodecode/templates/files/files_source.mako:133
10986 11038 msgid "Binary file ({})"
10987 11039 msgstr ""
10988 11040
10989 11041 #: rhodecode/templates/files/files_source_header.mako:33
10990 11042 msgid "File last commit"
10991 11043 msgstr ""
10992 11044
10993 11045 #: rhodecode/templates/files/files_upload.mako:4
10994 11046 msgid "{} Files Upload"
10995 11047 msgstr ""
10996 11048
10997 11049 #: rhodecode/templates/files/files_upload.mako:36
10998 11050 msgid "Uploading..."
10999 11051 msgstr ""
11000 11052
11001 11053 #: rhodecode/templates/files/files_upload.mako:38
11002 11054 msgid "Uploaded"
11003 11055 msgstr ""
11004 11056
11005 11057 #: rhodecode/templates/files/files_upload.mako:52
11006 11058 msgid "Upload new file"
11007 11059 msgstr ""
11008 11060
11009 11061 #: rhodecode/templates/files/files_upload.mako:109
11010 11062 msgid "Committing..."
11011 11063 msgstr ""
11012 11064
11013 11065 #: rhodecode/templates/files/files_upload.mako:110
11014 11066 msgid "Please wait while the files are being uploaded"
11015 11067 msgstr ""
11016 11068
11017 11069 #: rhodecode/templates/forks/fork.mako:5
11018 11070 #, python-format
11019 11071 msgid "Fork repository %s"
11020 11072 msgstr ""
11021 11073
11022 11074 #: rhodecode/templates/forks/fork.mako:30
11023 11075 #: rhodecode/templates/forks/forks.mako:60
11024 11076 msgid "Fork name"
11025 11077 msgstr ""
11026 11078
11027 11079 #: rhodecode/templates/forks/fork.mako:47
11028 11080 #, python-format
11029 11081 msgid "Select my personal group (%(repo_group_name)s)"
11030 11082 msgstr ""
11031 11083
11032 11084 #: rhodecode/templates/forks/fork.mako:78
11033 11085 msgid "Copy permissions"
11034 11086 msgstr ""
11035 11087
11036 11088 #: rhodecode/templates/forks/fork.mako:82
11037 11089 msgid "Copy permissions from parent repository."
11038 11090 msgstr ""
11039 11091
11040 11092 #: rhodecode/templates/forks/fork.mako:88
11041 11093 msgid "Private"
11042 11094 msgstr ""
11043 11095
11044 11096 #: rhodecode/templates/forks/fork.mako:97
11045 11097 msgid "Fork this Repository"
11046 11098 msgstr ""
11047 11099
11048 11100 #: rhodecode/templates/forks/forks.mako:5
11049 11101 #, python-format
11050 11102 msgid "%s Forks"
11051 11103 msgstr ""
11052 11104
11053 11105 #: rhodecode/templates/forks/forks.mako:28
11054 11106 msgid "Create new fork"
11055 11107 msgstr ""
11056 11108
11057 11109 #: rhodecode/templates/forks/forks.mako:64
11058 11110 msgid "Forked"
11059 11111 msgstr ""
11060 11112
11061 11113 #: rhodecode/templates/journal/journal.mako:25
11062 11114 msgid "ATOM journal feed"
11063 11115 msgstr ""
11064 11116
11065 11117 #: rhodecode/templates/journal/journal.mako:26
11066 11118 msgid "RSS journal feed"
11067 11119 msgstr ""
11068 11120
11069 11121 #: rhodecode/templates/journal/journal_data.mako:45
11070 11122 msgid "No entries yet"
11071 11123 msgstr ""
11072 11124
11073 11125 #: rhodecode/templates/journal/public_journal.mako:4
11074 11126 #: rhodecode/templates/journal/public_journal.mako:12
11075 11127 msgid "Public Journal"
11076 11128 msgstr ""
11077 11129
11078 11130 #: rhodecode/templates/journal/public_journal.mako:21
11079 11131 msgid "ATOM public journal feed"
11080 11132 msgstr ""
11081 11133
11082 11134 #: rhodecode/templates/journal/public_journal.mako:22
11083 11135 msgid "RSS public journal feed"
11084 11136 msgstr ""
11085 11137
11086 11138 #: rhodecode/templates/pullrequests/pullrequest.mako:5
11087 11139 msgid "New pull request"
11088 11140 msgstr ""
11089 11141
11090 11142 #: rhodecode/templates/pullrequests/pullrequest.mako:33
11091 11143 #: rhodecode/templates/pullrequests/pullrequest_show.mako:102
11092 11144 msgid "Commit flow"
11093 11145 msgstr ""
11094 11146
11095 11147 #: rhodecode/templates/pullrequests/pullrequest.mako:41
11096 11148 msgid "Source repository"
11097 11149 msgstr ""
11098 11150
11099 11151 #: rhodecode/templates/pullrequests/pullrequest.mako:69
11100 11152 msgid "Target repository"
11101 11153 msgstr ""
11102 11154
11103 11155 #: rhodecode/templates/pullrequests/pullrequest.mako:76
11104 11156 msgid "Loading refs..."
11105 11157 msgstr ""
11106 11158
11107 11159 #: rhodecode/templates/pullrequests/pullrequest.mako:115
11108 11160 msgid "Reviewers / Observers"
11109 11161 msgstr ""
11110 11162
11111 11163 #: rhodecode/templates/pullrequests/pullrequest.mako:121
11112 #: rhodecode/templates/pullrequests/pullrequest_show.mako:605
11164 #: rhodecode/templates/pullrequests/pullrequest_show.mako:621
11113 11165 msgid "Reviewer rules"
11114 11166 msgstr ""
11115 11167
11116 11168 #: rhodecode/templates/pullrequests/pullrequest.mako:167
11117 #: rhodecode/templates/pullrequests/pullrequest_show.mako:647
11169 #: rhodecode/templates/pullrequests/pullrequest_show.mako:643
11118 11170 msgid "Add reviewer or reviewer group"
11119 11171 msgstr ""
11120 11172
11121 11173 #: rhodecode/templates/pullrequests/pullrequest.mako:191
11122 #: rhodecode/templates/pullrequests/pullrequest_show.mako:700
11174 #: rhodecode/templates/pullrequests/pullrequest_show.mako:696
11123 11175 msgid "Add observer or observer group"
11124 11176 msgstr ""
11125 11177
11126 11178 #: rhodecode/templates/pullrequests/pullrequest.mako:216
11127 11179 msgid "Submit Pull Request"
11128 11180 msgstr ""
11129 11181
11130 11182 #: rhodecode/templates/pullrequests/pullrequest.mako:392
11131 11183 msgid "Show detailed compare."
11132 11184 msgstr ""
11133 11185
11134 11186 #: rhodecode/templates/pullrequests/pullrequest.mako:498
11135 11187 #: rhodecode/templates/pullrequests/pullrequest.mako:526
11136 11188 msgid "Select commit reference"
11137 11189 msgstr ""
11138 11190
11139 11191 #: rhodecode/templates/pullrequests/pullrequest.mako:594
11140 11192 msgid "Please select source and target"
11141 11193 msgstr ""
11142 11194
11143 11195 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:7
11144 11196 msgid "This pull request can be merged automatically."
11145 11197 msgstr ""
11146 11198
11147 11199 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:12
11148 11200 msgid "Merge is not currently possible because of below failed checks."
11149 11201 msgstr ""
11150 11202
11151 11203 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:57
11152 11204 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:61
11153 11205 msgid "Close with status {}"
11154 11206 msgstr ""
11155 11207
11156 11208 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:66
11157 11209 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:76
11158 11210 msgid "Merge and close Pull Request"
11159 11211 msgstr ""
11160 11212
11161 11213 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:70
11162 11214 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:75
11163 11215 msgid "refresh checks"
11164 11216 msgstr ""
11165 11217
11166 11218 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:76
11167 11219 msgid "You are not allowed to merge this pull request."
11168 11220 msgstr ""
11169 11221
11170 11222 #: rhodecode/templates/pullrequests/pullrequest_merge_checks.mako:78
11171 11223 msgid "Login to Merge this Pull Request"
11172 11224 msgstr ""
11173 11225
11174 11226 #: rhodecode/templates/pullrequests/pullrequest_show.mako:8
11175 11227 msgid "{} Pull Request !{}"
11176 11228 msgstr ""
11177 11229
11178 11230 #: rhodecode/templates/pullrequests/pullrequest_show.mako:59
11179 11231 msgid "Last updated on"
11180 11232 msgstr ""
11181 11233
11182 11234 #: rhodecode/templates/pullrequests/pullrequest_show.mako:60
11183 11235 msgid "by"
11184 11236 msgstr ""
11185 11237
11186 11238 #: rhodecode/templates/pullrequests/pullrequest_show.mako:69
11187 11239 msgid "Update title & description"
11188 11240 msgstr ""
11189 11241
11190 11242 #: rhodecode/templates/pullrequests/pullrequest_show.mako:75
11191 11243 #: rhodecode/templates/pullrequests/pullrequest_show.mako:78
11192 11244 msgid "Delete pull request"
11193 11245 msgstr ""
11194 11246
11195 11247 #: rhodecode/templates/pullrequests/pullrequest_show.mako:78
11196 11248 msgid "Not allowed to delete this pull request"
11197 11249 msgstr ""
11198 11250
11199 11251 #: rhodecode/templates/pullrequests/pullrequest_show.mako:88
11200 11252 msgid "Rendered using {} renderer"
11201 11253 msgstr ""
11202 11254
11203 11255 #: rhodecode/templates/pullrequests/pullrequest_show.mako:135
11204 11256 msgid "Common ancestor"
11205 11257 msgstr ""
11206 11258
11207 11259 #: rhodecode/templates/pullrequests/pullrequest_show.mako:139
11208 11260 msgid "not available"
11209 11261 msgstr ""
11210 11262
11211 11263 #: rhodecode/templates/pullrequests/pullrequest_show.mako:151
11212 11264 msgid "Pull changes from source"
11213 11265 msgstr ""
11214 11266
11215 11267 #: rhodecode/templates/pullrequests/pullrequest_show.mako:152
11216 11268 msgid "Copy the pull url"
11217 11269 msgstr ""
11218 11270
11219 11271 #: rhodecode/templates/pullrequests/pullrequest_show.mako:164
11220 11272 msgid "Clone repository in its merged state using shadow repository"
11221 11273 msgstr ""
11222 11274
11223 11275 #: rhodecode/templates/pullrequests/pullrequest_show.mako:164
11224 11276 msgid "Clone from shadow repository"
11225 11277 msgstr ""
11226 11278
11227 11279 #: rhodecode/templates/pullrequests/pullrequest_show.mako:165
11228 11280 #: rhodecode/templates/summary/components.mako:78
11229 11281 msgid "Copy the clone url"
11230 11282 msgstr ""
11231 11283
11232 11284 #: rhodecode/templates/pullrequests/pullrequest_show.mako:169
11233 11285 msgid "Shadow repository data not available"
11234 11286 msgstr ""
11235 11287
11236 11288 #: rhodecode/templates/pullrequests/pullrequest_show.mako:185
11237 11289 msgid "Versions"
11238 11290 msgstr ""
11239 11291
11240 11292 #: rhodecode/templates/pullrequests/pullrequest_show.mako:197
11241 11293 #: rhodecode/templates/pullrequests/pullrequest_show.mako:199
11242 11294 msgid "show versions"
11243 11295 msgstr ""
11244 11296
11245 11297 #: rhodecode/templates/pullrequests/pullrequest_show.mako:198
11246 11298 msgid "hide versions"
11247 11299 msgstr ""
11248 11300
11249 11301 #: rhodecode/templates/pullrequests/pullrequest_show.mako:223
11250 11302 msgid "Your review status at this version"
11251 11303 msgstr ""
11252 11304
11253 11305 #: rhodecode/templates/pullrequests/pullrequest_show.mako:228
11254 11306 msgid "Comments from pull request version v{0}"
11255 11307 msgstr ""
11256 11308
11257 11309 #: rhodecode/templates/pullrequests/pullrequest_show.mako:246
11258 11310 #: rhodecode/templates/pullrequests/pullrequest_show.mako:250
11259 11311 msgid "select versions to show changes"
11260 11312 msgstr ""
11261 11313
11262 11314 #: rhodecode/templates/pullrequests/pullrequest_show.mako:247
11263 11315 msgid "show changes between versions"
11264 11316 msgstr ""
11265 11317
11266 11318 #: rhodecode/templates/pullrequests/pullrequest_show.mako:248
11267 11319 msgid "show pull request for this version"
11268 11320 msgstr ""
11269 11321
11270 11322 #: rhodecode/templates/pullrequests/pullrequest_show.mako:257
11271 11323 msgid "Pull request versions not available"
11272 11324 msgstr ""
11273 11325
11274 11326 #: rhodecode/templates/pullrequests/pullrequest_show.mako:277
11275 11327 msgid "Cannot show diff when pull request state is changing. Current progress state"
11276 11328 msgstr ""
11277 11329
11278 11330 #: rhodecode/templates/pullrequests/pullrequest_show.mako:295
11279 11331 msgid "Missing requirements:"
11280 11332 msgstr ""
11281 11333
11282 11334 #: rhodecode/templates/pullrequests/pullrequest_show.mako:296
11283 11335 msgid "These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled."
11284 11336 msgstr ""
11285 11337
11286 11338 #: rhodecode/templates/pullrequests/pullrequest_show.mako:304
11287 11339 msgid "Missing commits"
11288 11340 msgstr ""
11289 11341
11290 11342 #: rhodecode/templates/pullrequests/pullrequest_show.mako:305
11291 11343 msgid "This pull request cannot be displayed, because one or more commits no longer exist in the source repository."
11292 11344 msgstr ""
11293 11345
11294 11346 #: rhodecode/templates/pullrequests/pullrequest_show.mako:306
11295 11347 msgid "Please update this pull request, push the commits back into the source repository, or consider closing this pull request."
11296 11348 msgstr ""
11297 11349
11298 11350 #: rhodecode/templates/pullrequests/pullrequest_show.mako:307
11299 11351 msgid "Consider doing a `force update commits` in case you think this is an error."
11300 11352 msgstr ""
11301 11353
11302 11354 #: rhodecode/templates/pullrequests/pullrequest_show.mako:315
11303 11355 msgid "There are new changes for `{}:{}` in source repository, please consider updating this pull request."
11304 11356 msgstr ""
11305 11357
11306 11358 #: rhodecode/templates/pullrequests/pullrequest_show.mako:326
11307 11359 msgid "Showing changes at v{}, commenting is disabled."
11308 11360 msgstr ""
11309 11361
11310 11362 #: rhodecode/templates/pullrequests/pullrequest_show.mako:349
11311 11363 #: rhodecode/templates/pullrequests/pullrequest_show.mako:371
11312 11364 msgid "Update commits"
11313 11365 msgstr ""
11314 11366
11315 11367 #: rhodecode/templates/pullrequests/pullrequest_show.mako:352
11316 11368 msgid "more update options"
11317 11369 msgstr ""
11318 11370
11319 11371 #: rhodecode/templates/pullrequests/pullrequest_show.mako:360
11320 11372 msgid "Force update commits"
11321 11373 msgstr ""
11322 11374
11323 11375 #: rhodecode/templates/pullrequests/pullrequest_show.mako:363
11324 11376 msgid "Update commits and force refresh this pull request."
11325 11377 msgstr ""
11326 11378
11327 11379 #: rhodecode/templates/pullrequests/pullrequest_show.mako:371
11328 11380 msgid "Update is disabled for current view"
11329 11381 msgstr ""
11330 11382
11331 11383 #: rhodecode/templates/pullrequests/pullrequest_show.mako:383
11332 11384 msgid "Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled"
11333 11385 msgstr ""
11334 11386
11335 11387 #: rhodecode/templates/pullrequests/pullrequest_show.mako:387
11336 11388 msgid "commits added: {}, removed: {}"
11337 11389 msgstr ""
11338 11390
11339 11391 #: rhodecode/templates/pullrequests/pullrequest_show.mako:405
11340 11392 msgid "Commit added in displayed changes"
11341 11393 msgstr ""
11342 11394
11343 11395 #: rhodecode/templates/pullrequests/pullrequest_show.mako:407
11344 11396 msgid "Commit removed in displayed changes"
11345 11397 msgstr ""
11346 11398
11347 11399 #: rhodecode/templates/pullrequests/pullrequest_show.mako:510
11348 11400 msgid "there is {num} general comment from older versions"
11349 11401 msgstr ""
11350 11402
11351 11403 #: rhodecode/templates/pullrequests/pullrequest_show.mako:511
11352 11404 msgid "show it"
11353 11405 msgstr ""
11354 11406
11355 11407 #: rhodecode/templates/pullrequests/pullrequest_show.mako:513
11356 11408 msgid "there are {num} general comments from older versions"
11357 11409 msgstr ""
11358 11410
11359 11411 #: rhodecode/templates/pullrequests/pullrequest_show.mako:514
11360 11412 msgid "show them"
11361 11413 msgstr ""
11362 11414
11363 11415 #: rhodecode/templates/pullrequests/pullrequest_show.mako:556
11364 11416 #: rhodecode/templates/pullrequests/pullrequest_show.mako:567
11365 11417 msgid "Drafts"
11366 11418 msgstr ""
11367 11419
11368 11420 #: rhodecode/templates/pullrequests/pullrequest_show.mako:569
11369 11421 msgid "Submit"
11370 11422 msgstr ""
11371 11423
11372 #: rhodecode/templates/pullrequests/pullrequest_show.mako:625
11424 #: rhodecode/templates/pullrequests/pullrequest_show.mako:612
11373 11425 msgid "Show rules"
11374 11426 msgstr ""
11375 11427
11376 #: rhodecode/templates/pullrequests/pullrequest_show.mako:652
11377 #: rhodecode/templates/pullrequests/pullrequest_show.mako:705
11428 #: rhodecode/templates/pullrequests/pullrequest_show.mako:648
11429 #: rhodecode/templates/pullrequests/pullrequest_show.mako:701
11378 11430 msgid "Save Changes"
11379 11431 msgstr ""
11380 11432
11381 #: rhodecode/templates/pullrequests/pullrequest_show.mako:676
11433 #: rhodecode/templates/pullrequests/pullrequest_show.mako:672
11382 11434 msgid "Observers"
11383 11435 msgstr ""
11384 11436
11385 #: rhodecode/templates/pullrequests/pullrequest_show.mako:742
11437 #: rhodecode/templates/pullrequests/pullrequest_show.mako:738
11386 11438 msgid "TODOs unavailable when browsing versions"
11387 11439 msgstr ""
11388 11440
11389 #: rhodecode/templates/pullrequests/pullrequest_show.mako:814
11390 #: rhodecode/templates/pullrequests/pullrequest_show.mako:822
11441 #: rhodecode/templates/pullrequests/pullrequest_show.mako:810
11442 #: rhodecode/templates/pullrequests/pullrequest_show.mako:818
11391 11443 msgid "Referenced Tickets"
11392 11444 msgstr ""
11393 11445
11394 #: rhodecode/templates/pullrequests/pullrequest_show.mako:828
11446 #: rhodecode/templates/pullrequests/pullrequest_show.mako:824
11395 11447 msgid "In pull request description"
11396 11448 msgstr ""
11397 11449
11398 #: rhodecode/templates/pullrequests/pullrequest_show.mako:842
11399 #: rhodecode/templates/pullrequests/pullrequest_show.mako:861
11450 #: rhodecode/templates/pullrequests/pullrequest_show.mako:838
11451 #: rhodecode/templates/pullrequests/pullrequest_show.mako:857
11400 11452 msgid "No Ticket data found."
11401 11453 msgstr ""
11402 11454
11403 #: rhodecode/templates/pullrequests/pullrequest_show.mako:847
11455 #: rhodecode/templates/pullrequests/pullrequest_show.mako:843
11404 11456 msgid "In commit messages"
11405 11457 msgstr ""
11406 11458
11407 11459 #: rhodecode/templates/pullrequests/pullrequests.mako:4
11408 11460 msgid "{} Pull Requests"
11409 11461 msgstr ""
11410 11462
11411 11463 #: rhodecode/templates/pullrequests/pullrequests.mako:27
11412 11464 msgid "Opened"
11413 11465 msgstr ""
11414 11466
11415 11467 #: rhodecode/templates/pullrequests/pullrequests.mako:28
11416 11468 msgid "Opened by me"
11417 11469 msgstr ""
11418 11470
11419 11471 #: rhodecode/templates/pullrequests/pullrequests.mako:29
11420 11472 msgid "Awaiting review"
11421 11473 msgstr ""
11422 11474
11423 11475 #: rhodecode/templates/pullrequests/pullrequests.mako:30
11424 11476 msgid "Awaiting my review"
11425 11477 msgstr ""
11426 11478
11427 11479 #: rhodecode/templates/pullrequests/pullrequests.mako:32
11428 11480 msgid "From this repo"
11429 11481 msgstr ""
11430 11482
11431 11483 #: rhodecode/templates/pullrequests/pullrequests.mako:40
11432 11484 msgid "Open new Pull Request"
11433 11485 msgstr ""
11434 11486
11435 11487 #: rhodecode/templates/search/search.mako:6
11436 11488 #: rhodecode/templates/search/search.mako:19
11437 11489 msgid "Search inside repository {repo_name}"
11438 11490 msgstr ""
11439 11491
11440 11492 #: rhodecode/templates/search/search.mako:8
11441 11493 #: rhodecode/templates/search/search.mako:21
11442 11494 msgid "Search inside repository group {repo_group_name}"
11443 11495 msgstr ""
11444 11496
11445 11497 #: rhodecode/templates/search/search.mako:10
11446 11498 #: rhodecode/templates/search/search.mako:23
11447 11499 msgid "Search inside all accessible repositories"
11448 11500 msgstr ""
11449 11501
11450 11502 #: rhodecode/templates/search/search.mako:99
11451 11503 msgid "File path"
11452 11504 msgstr ""
11453 11505
11454 11506 #: rhodecode/templates/search/search.mako:102
11455 11507 msgid "Search"
11456 11508 msgstr ""
11457 11509
11458 11510 #: rhodecode/templates/search/search.mako:108
11459 11511 #: rhodecode/templates/search/search.mako:110
11460 11512 #: rhodecode/templates/search/search.mako:112
11461 11513 msgid "Global Search"
11462 11514 msgstr ""
11463 11515
11464 11516 #: rhodecode/templates/search/search.mako:138
11465 11517 msgid "sort"
11466 11518 msgstr ""
11467 11519
11468 11520 #: rhodecode/templates/search/search.mako:157
11469 11521 msgid "Query Language examples"
11470 11522 msgstr ""
11471 11523
11472 11524 #: rhodecode/templates/search/search_commit.mako:15
11473 11525 msgid "Commit date"
11474 11526 msgstr ""
11475 11527
11476 11528 #: rhodecode/templates/search/search_content.mako:7
11477 11529 msgid "No content matched"
11478 11530 msgstr ""
11479 11531
11480 11532 #: rhodecode/templates/search/search_content.mako:12
11481 11533 msgid "more matches in this file"
11482 11534 msgstr ""
11483 11535
11484 11536 #: rhodecode/templates/search/search_content.mako:112
11485 11537 msgid "Narrow to this repository group"
11486 11538 msgstr ""
11487 11539
11488 11540 #: rhodecode/templates/search/search_content.mako:119
11489 11541 msgid "Narrow to this repository"
11490 11542 msgstr ""
11491 11543
11492 11544 #: rhodecode/templates/search/search_path.mako:15
11493 11545 msgid "Lines"
11494 11546 msgstr ""
11495 11547
11496 11548 #: rhodecode/templates/summary/components.mako:20
11497 11549 msgid "Closed Branch"
11498 11550 msgstr ""
11499 11551
11500 11552 #: rhodecode/templates/summary/components.mako:83
11501 11553 msgid "Copy the clone by id url"
11502 11554 msgstr ""
11503 11555
11504 11556 #: rhodecode/templates/summary/components.mako:88
11505 11557 msgid "Copy the clone by ssh url"
11506 11558 msgstr ""
11507 11559
11508 11560 #: rhodecode/templates/summary/components.mako:92
11509 11561 msgid "SVN Protocol is disabled. To enable it, see the"
11510 11562 msgstr ""
11511 11563
11512 11564 #: rhodecode/templates/summary/components.mako:92
11513 11565 msgid "documentation here"
11514 11566 msgstr ""
11515 11567
11516 11568 #: rhodecode/templates/summary/components.mako:135
11517 11569 msgid "Number of Repository Forks"
11518 11570 msgstr ""
11519 11571
11520 11572 #: rhodecode/templates/summary/components.mako:172
11521 11573 msgid "Downloads"
11522 11574 msgstr ""
11523 11575
11524 11576 #: rhodecode/templates/summary/components.mako:177
11525 11577 msgid "There are no downloads yet"
11526 11578 msgstr ""
11527 11579
11528 11580 #: rhodecode/templates/summary/components.mako:181
11529 11581 msgid "Downloads are disabled for this repository"
11530 11582 msgstr ""
11531 11583
11532 11584 #: rhodecode/templates/summary/components.mako:227
11533 11585 msgid "Repository size"
11534 11586 msgstr ""
11535 11587
11536 11588 #: rhodecode/templates/summary/components.mako:239
11537 11589 msgid "Calculating Repository Size..."
11538 11590 msgstr ""
11539 11591
11540 11592 #: rhodecode/templates/summary/components.mako:250
11541 11593 msgid "Code Statistics"
11542 11594 msgstr ""
11543 11595
11544 11596 #: rhodecode/templates/summary/components.mako:259
11545 11597 msgid "Statistics are disabled for this repository"
11546 11598 msgstr ""
11547 11599
11548 11600 #: rhodecode/templates/summary/summary.mako:21
11549 11601 msgid "Quick start"
11550 11602 msgstr ""
11551 11603
11552 11604 #: rhodecode/templates/summary/summary_base.mako:5
11553 11605 msgid "{} Summary"
11554 11606 msgstr ""
11555 11607
11556 11608 #: rhodecode/templates/summary/summary_base.mako:13
11557 11609 #, python-format
11558 11610 msgid "%s ATOM feed"
11559 11611 msgstr ""
11560 11612
11561 11613 #: rhodecode/templates/summary/summary_base.mako:14
11562 11614 #, python-format
11563 11615 msgid "%s RSS feed"
11564 11616 msgstr ""
11565 11617
11566 11618 #: rhodecode/templates/summary/summary_commits.mako:117
11567 11619 msgid "Add or upload files directly via RhodeCode:"
11568 11620 msgstr ""
11569 11621
11570 11622 #: rhodecode/templates/summary/summary_commits.mako:119
11571 11623 msgid "Add New File"
11572 11624 msgstr ""
11573 11625
11574 11626 #: rhodecode/templates/summary/summary_commits.mako:122
11575 11627 msgid "Upload New File"
11576 11628 msgstr ""
11577 11629
11578 11630 #: rhodecode/templates/summary/summary_commits.mako:128
11579 11631 msgid "Push new repo:"
11580 11632 msgstr ""
11581 11633
11582 11634 #: rhodecode/templates/summary/summary_commits.mako:151
11583 11635 msgid "Existing repository?"
11584 11636 msgstr ""
11585 11637
11586 11638 #: rhodecode/templates/tags/tags.mako:5
11587 11639 #, python-format
11588 11640 msgid "%s Tags"
11589 11641 msgstr ""
11590 11642
11591 11643 #: rhodecode/templates/tags/tags.mako:28
11592 11644 msgid "Compare Selected Tags"
11593 11645 msgstr ""
11594 11646
11595 11647 #: rhodecode/templates/user_group/profile.mako:6
11596 11648 msgid "User Group Profile"
11597 11649 msgstr ""
11598 11650
11599 11651 #: rhodecode/templates/user_group/profile.mako:15
11600 11652 msgid "Group Name"
11601 11653 msgstr ""
11602 11654
11603 11655 #: rhodecode/templates/users/user_profile.mako:39
11604 11656 msgid "First name"
11605 11657 msgstr ""
11606 11658
11607 11659 #: rhodecode/templates/users/user_profile.mako:49
11608 11660 msgid "Last name"
11609 11661 msgstr ""
11610 11662
11611 11663 #: rhodecode/tests/lib/test_ext_json.py:162
11612 11664 msgid "hello"
11613 11665 msgstr ""
11614 11666
@@ -1,626 +1,627 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 The base Controller API
23 23 Provides the BaseController class for subclassing. And usage in different
24 24 controllers
25 25 """
26 26
27 27 import logging
28 28 import socket
29 29
30 30 import markupsafe
31 31 import ipaddress
32 32
33 33 from paste.auth.basic import AuthBasicAuthenticator
34 34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 36
37 37 import rhodecode
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.authentication.base import VCS_TYPE
40 40 from rhodecode.lib import auth, utils2
41 41 from rhodecode.lib import helpers as h
42 42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 43 from rhodecode.lib.exceptions import UserCreationError
44 44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 45 from rhodecode.lib.utils2 import (
46 46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 48 from rhodecode.model.notification import NotificationModel
49 49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def _filter_proxy(ip):
55 55 """
56 56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 57 ips. Those comma separated IPs are passed from various proxies in the
58 58 chain of request processing. The left-most being the original client.
59 59 We only care about the first IP which came from the org. client.
60 60
61 61 :param ip: ip string from headers
62 62 """
63 63 if ',' in ip:
64 64 _ips = ip.split(',')
65 65 _first_ip = _ips[0].strip()
66 66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 67 return _first_ip
68 68 return ip
69 69
70 70
71 71 def _filter_port(ip):
72 72 """
73 73 Removes a port from ip, there are 4 main cases to handle here.
74 74 - ipv4 eg. 127.0.0.1
75 75 - ipv6 eg. ::1
76 76 - ipv4+port eg. 127.0.0.1:8080
77 77 - ipv6+port eg. [::1]:8080
78 78
79 79 :param ip:
80 80 """
81 81 def is_ipv6(ip_addr):
82 82 if hasattr(socket, 'inet_pton'):
83 83 try:
84 84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 85 except socket.error:
86 86 return False
87 87 else:
88 88 # fallback to ipaddress
89 89 try:
90 90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 91 except Exception:
92 92 return False
93 93 return True
94 94
95 95 if ':' not in ip: # must be ipv4 pure ip
96 96 return ip
97 97
98 98 if '[' in ip and ']' in ip: # ipv6 with port
99 99 return ip.split(']')[0][1:].lower()
100 100
101 101 # must be ipv6 or ipv4 with port
102 102 if is_ipv6(ip):
103 103 return ip
104 104 else:
105 105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 106 return ip
107 107
108 108
109 109 def get_ip_addr(environ):
110 110 proxy_key = 'HTTP_X_REAL_IP'
111 111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 112 def_key = 'REMOTE_ADDR'
113 113 _filters = lambda x: _filter_port(_filter_proxy(x))
114 114
115 115 ip = environ.get(proxy_key)
116 116 if ip:
117 117 return _filters(ip)
118 118
119 119 ip = environ.get(proxy_key2)
120 120 if ip:
121 121 return _filters(ip)
122 122
123 123 ip = environ.get(def_key, '0.0.0.0')
124 124 return _filters(ip)
125 125
126 126
127 127 def get_server_ip_addr(environ, log_errors=True):
128 128 hostname = environ.get('SERVER_NAME')
129 129 try:
130 130 return socket.gethostbyname(hostname)
131 131 except Exception as e:
132 132 if log_errors:
133 133 # in some cases this lookup is not possible, and we don't want to
134 134 # make it an exception in logs
135 135 log.exception('Could not retrieve server ip address: %s', e)
136 136 return hostname
137 137
138 138
139 139 def get_server_port(environ):
140 140 return environ.get('SERVER_PORT')
141 141
142 142
143 143 def get_access_path(environ):
144 144 path = environ.get('PATH_INFO')
145 145 org_req = environ.get('pylons.original_request')
146 146 if org_req:
147 147 path = org_req.environ.get('PATH_INFO')
148 148 return path
149 149
150 150
151 151 def get_user_agent(environ):
152 152 return environ.get('HTTP_USER_AGENT')
153 153
154 154
155 155 def vcs_operation_context(
156 156 environ, repo_name, username, action, scm, check_locking=True,
157 157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 158 """
159 159 Generate the context for a vcs operation, e.g. push or pull.
160 160
161 161 This context is passed over the layers so that hooks triggered by the
162 162 vcs operation know details like the user, the user's IP address etc.
163 163
164 164 :param check_locking: Allows to switch of the computation of the locking
165 165 data. This serves mainly the need of the simplevcs middleware to be
166 166 able to disable this for certain operations.
167 167
168 168 """
169 169 # Tri-state value: False: unlock, None: nothing, True: lock
170 170 make_lock = None
171 171 locked_by = [None, None, None]
172 172 is_anonymous = username == User.DEFAULT_USER
173 173 user = User.get_by_username(username)
174 174 if not is_anonymous and check_locking:
175 175 log.debug('Checking locking on repository "%s"', repo_name)
176 176 repo = Repository.get_by_repo_name(repo_name)
177 177 make_lock, __, locked_by = repo.get_locking_state(
178 178 action, user.user_id)
179 179 user_id = user.user_id
180 180 settings_model = VcsSettingsModel(repo=repo_name)
181 181 ui_settings = settings_model.get_ui_settings()
182 182
183 183 # NOTE(marcink): This should be also in sync with
184 184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 185 store = [x for x in ui_settings if x.key == '/']
186 186 repo_store = ''
187 187 if store:
188 188 repo_store = store[0].value
189 189
190 190 scm_data = {
191 191 'ip': get_ip_addr(environ),
192 192 'username': username,
193 193 'user_id': user_id,
194 194 'action': action,
195 195 'repository': repo_name,
196 196 'scm': scm,
197 197 'config': rhodecode.CONFIG['__file__'],
198 198 'repo_store': repo_store,
199 199 'make_lock': make_lock,
200 200 'locked_by': locked_by,
201 201 'server_url': utils2.get_server_url(environ),
202 202 'user_agent': get_user_agent(environ),
203 203 'hooks': get_enabled_hook_classes(ui_settings),
204 204 'is_shadow_repo': is_shadow_repo,
205 205 'detect_force_push': detect_force_push,
206 206 'check_branch_perms': check_branch_perms,
207 207 }
208 208 return scm_data
209 209
210 210
211 211 class BasicAuth(AuthBasicAuthenticator):
212 212
213 213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
215 215 self.realm = realm
216 216 self.rc_realm = rc_realm
217 217 self.initial_call = initial_call_detection
218 218 self.authfunc = authfunc
219 219 self.registry = registry
220 220 self.acl_repo_name = acl_repo_name
221 221 self._rc_auth_http_code = auth_http_code
222 222
223 223 def _get_response_from_code(self, http_code):
224 224 try:
225 225 return get_exception(safe_int(http_code))
226 226 except Exception:
227 227 log.exception('Failed to fetch response for code %s', http_code)
228 228 return HTTPForbidden
229 229
230 230 def get_rc_realm(self):
231 231 return safe_str(self.rc_realm)
232 232
233 233 def build_authentication(self):
234 234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
235 235 if self._rc_auth_http_code and not self.initial_call:
236 236 # return alternative HTTP code if alternative http return code
237 237 # is specified in RhodeCode config, but ONLY if it's not the
238 238 # FIRST call
239 239 custom_response_klass = self._get_response_from_code(
240 240 self._rc_auth_http_code)
241 241 return custom_response_klass(headers=head)
242 242 return HTTPUnauthorized(headers=head)
243 243
244 244 def authenticate(self, environ):
245 245 authorization = AUTHORIZATION(environ)
246 246 if not authorization:
247 247 return self.build_authentication()
248 248 (authmeth, auth) = authorization.split(' ', 1)
249 249 if 'basic' != authmeth.lower():
250 250 return self.build_authentication()
251 251 auth = auth.strip().decode('base64')
252 252 _parts = auth.split(':', 1)
253 253 if len(_parts) == 2:
254 254 username, password = _parts
255 255 auth_data = self.authfunc(
256 256 username, password, environ, VCS_TYPE,
257 257 registry=self.registry, acl_repo_name=self.acl_repo_name)
258 258 if auth_data:
259 259 return {'username': username, 'auth_data': auth_data}
260 260 if username and password:
261 261 # we mark that we actually executed authentication once, at
262 262 # that point we can use the alternative auth code
263 263 self.initial_call = False
264 264
265 265 return self.build_authentication()
266 266
267 267 __call__ = authenticate
268 268
269 269
270 270 def calculate_version_hash(config):
271 271 return sha1(
272 272 config.get('beaker.session.secret', '') +
273 273 rhodecode.__version__)[:8]
274 274
275 275
276 276 def get_current_lang(request):
277 277 # NOTE(marcink): remove after pyramid move
278 278 try:
279 279 return translation.get_lang()[0]
280 280 except:
281 281 pass
282 282
283 283 return getattr(request, '_LOCALE_', request.locale_name)
284 284
285 285
286 286 def attach_context_attributes(context, request, user_id=None, is_api=None):
287 287 """
288 288 Attach variables into template context called `c`.
289 289 """
290 290 config = request.registry.settings
291 291
292 292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
293 293 context.rc_config = rc_config
294 294 context.rhodecode_version = rhodecode.__version__
295 295 context.rhodecode_edition = config.get('rhodecode.edition')
296 296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
297 297 # unique secret + version does not leak the version but keep consistency
298 298 context.rhodecode_version_hash = calculate_version_hash(config)
299 299
300 300 # Default language set for the incoming request
301 301 context.language = get_current_lang(request)
302 302
303 303 # Visual options
304 304 context.visual = AttributeDict({})
305 305
306 306 # DB stored Visual Items
307 307 context.visual.show_public_icon = str2bool(
308 308 rc_config.get('rhodecode_show_public_icon'))
309 309 context.visual.show_private_icon = str2bool(
310 310 rc_config.get('rhodecode_show_private_icon'))
311 311 context.visual.stylify_metatags = str2bool(
312 312 rc_config.get('rhodecode_stylify_metatags'))
313 313 context.visual.dashboard_items = safe_int(
314 314 rc_config.get('rhodecode_dashboard_items', 100))
315 315 context.visual.admin_grid_items = safe_int(
316 316 rc_config.get('rhodecode_admin_grid_items', 100))
317 317 context.visual.show_revision_number = str2bool(
318 318 rc_config.get('rhodecode_show_revision_number', True))
319 319 context.visual.show_sha_length = safe_int(
320 320 rc_config.get('rhodecode_show_sha_length', 100))
321 321 context.visual.repository_fields = str2bool(
322 322 rc_config.get('rhodecode_repository_fields'))
323 323 context.visual.show_version = str2bool(
324 324 rc_config.get('rhodecode_show_version'))
325 325 context.visual.use_gravatar = str2bool(
326 326 rc_config.get('rhodecode_use_gravatar'))
327 327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
328 328 context.visual.default_renderer = rc_config.get(
329 329 'rhodecode_markup_renderer', 'rst')
330 330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
331 331 context.visual.rhodecode_support_url = \
332 332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
333 333
334 334 context.visual.affected_files_cut_off = 60
335 335
336 336 context.pre_code = rc_config.get('rhodecode_pre_code')
337 337 context.post_code = rc_config.get('rhodecode_post_code')
338 338 context.rhodecode_name = rc_config.get('rhodecode_title')
339 339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
340 340 # if we have specified default_encoding in the request, it has more
341 341 # priority
342 342 if request.GET.get('default_encoding'):
343 343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
344 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 346 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
346 347
347 348 # INI stored
348 349 context.labs_active = str2bool(
349 350 config.get('labs_settings_active', 'false'))
350 351 context.ssh_enabled = str2bool(
351 352 config.get('ssh.generate_authorized_keyfile', 'false'))
352 353 context.ssh_key_generator_enabled = str2bool(
353 354 config.get('ssh.enable_ui_key_generator', 'true'))
354 355
355 356 context.visual.allow_repo_location_change = str2bool(
356 357 config.get('allow_repo_location_change', True))
357 358 context.visual.allow_custom_hooks_settings = str2bool(
358 359 config.get('allow_custom_hooks_settings', True))
359 360 context.debug_style = str2bool(config.get('debug_style', False))
360 361
361 362 context.rhodecode_instanceid = config.get('instance_id')
362 363
363 364 context.visual.cut_off_limit_diff = safe_int(
364 365 config.get('cut_off_limit_diff'))
365 366 context.visual.cut_off_limit_file = safe_int(
366 367 config.get('cut_off_limit_file'))
367 368
368 369 context.license = AttributeDict({})
369 370 context.license.hide_license_info = str2bool(
370 371 config.get('license.hide_license_info', False))
371 372
372 373 # AppEnlight
373 374 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
374 375 context.appenlight_api_public_key = config.get(
375 376 'appenlight.api_public_key', '')
376 377 context.appenlight_server_url = config.get('appenlight.server_url', '')
377 378
378 379 diffmode = {
379 380 "unified": "unified",
380 381 "sideside": "sideside"
381 382 }.get(request.GET.get('diffmode'))
382 383
383 384 if is_api is not None:
384 385 is_api = hasattr(request, 'rpc_user')
385 386 session_attrs = {
386 387 # defaults
387 388 "clone_url_format": "http",
388 389 "diffmode": "sideside",
389 390 "license_fingerprint": request.session.get('license_fingerprint')
390 391 }
391 392
392 393 if not is_api:
393 394 # don't access pyramid session for API calls
394 395 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
395 396 request.session['rc_user_session_attr.diffmode'] = diffmode
396 397
397 398 # session settings per user
398 399
399 400 for k, v in request.session.items():
400 401 pref = 'rc_user_session_attr.'
401 402 if k and k.startswith(pref):
402 403 k = k[len(pref):]
403 404 session_attrs[k] = v
404 405
405 406 context.user_session_attrs = session_attrs
406 407
407 408 # JS template context
408 409 context.template_context = {
409 410 'repo_name': None,
410 411 'repo_type': None,
411 412 'repo_landing_commit': None,
412 413 'rhodecode_user': {
413 414 'username': None,
414 415 'email': None,
415 416 'notification_status': False
416 417 },
417 418 'session_attrs': session_attrs,
418 419 'visual': {
419 420 'default_renderer': None
420 421 },
421 422 'commit_data': {
422 423 'commit_id': None
423 424 },
424 425 'pull_request_data': {'pull_request_id': None},
425 426 'timeago': {
426 427 'refresh_time': 120 * 1000,
427 428 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
428 429 },
429 430 'pyramid_dispatch': {
430 431
431 432 },
432 433 'extra': {'plugins': {}}
433 434 }
434 435 # END CONFIG VARS
435 436 if is_api:
436 437 csrf_token = None
437 438 else:
438 439 csrf_token = auth.get_csrf_token(session=request.session)
439 440
440 441 context.csrf_token = csrf_token
441 442 context.backends = rhodecode.BACKENDS.keys()
442 443
443 444 unread_count = 0
444 445 user_bookmark_list = []
445 446 if user_id:
446 447 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
447 448 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
448 449 context.unread_notifications = unread_count
449 450 context.bookmark_items = user_bookmark_list
450 451
451 452 # web case
452 453 if hasattr(request, 'user'):
453 454 context.auth_user = request.user
454 455 context.rhodecode_user = request.user
455 456
456 457 # api case
457 458 if hasattr(request, 'rpc_user'):
458 459 context.auth_user = request.rpc_user
459 460 context.rhodecode_user = request.rpc_user
460 461
461 462 # attach the whole call context to the request
462 463 request.call_context = context
463 464
464 465
465 466 def get_auth_user(request):
466 467 environ = request.environ
467 468 session = request.session
468 469
469 470 ip_addr = get_ip_addr(environ)
470 471
471 472 # make sure that we update permissions each time we call controller
472 473 _auth_token = (
473 474 # ?auth_token=XXX
474 475 request.GET.get('auth_token', '')
475 476 # ?api_key=XXX !LEGACY
476 477 or request.GET.get('api_key', '')
477 478 # or headers....
478 479 or request.headers.get('X-Rc-Auth-Token', '')
479 480 )
480 481 if not _auth_token and request.matchdict:
481 482 url_auth_token = request.matchdict.get('_auth_token')
482 483 _auth_token = url_auth_token
483 484 if _auth_token:
484 485 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
485 486
486 487 if _auth_token:
487 488 # when using API_KEY we assume user exists, and
488 489 # doesn't need auth based on cookies.
489 490 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
490 491 authenticated = False
491 492 else:
492 493 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
493 494 try:
494 495 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
495 496 ip_addr=ip_addr)
496 497 except UserCreationError as e:
497 498 h.flash(e, 'error')
498 499 # container auth or other auth functions that create users
499 500 # on the fly can throw this exception signaling that there's
500 501 # issue with user creation, explanation should be provided
501 502 # in Exception itself. We then create a simple blank
502 503 # AuthUser
503 504 auth_user = AuthUser(ip_addr=ip_addr)
504 505
505 506 # in case someone changes a password for user it triggers session
506 507 # flush and forces a re-login
507 508 if password_changed(auth_user, session):
508 509 session.invalidate()
509 510 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
510 511 auth_user = AuthUser(ip_addr=ip_addr)
511 512
512 513 authenticated = cookie_store.get('is_authenticated')
513 514
514 515 if not auth_user.is_authenticated and auth_user.is_user_object:
515 516 # user is not authenticated and not empty
516 517 auth_user.set_authenticated(authenticated)
517 518
518 519 return auth_user, _auth_token
519 520
520 521
521 522 def h_filter(s):
522 523 """
523 524 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
524 525 we wrap this with additional functionality that converts None to empty
525 526 strings
526 527 """
527 528 if s is None:
528 529 return markupsafe.Markup()
529 530 return markupsafe.escape(s)
530 531
531 532
532 533 def add_events_routes(config):
533 534 """
534 535 Adds routing that can be used in events. Because some events are triggered
535 536 outside of pyramid context, we need to bootstrap request with some
536 537 routing registered
537 538 """
538 539
539 540 from rhodecode.apps._base import ADMIN_PREFIX
540 541
541 542 config.add_route(name='home', pattern='/')
542 543 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
543 544 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
544 545
545 546 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
546 547 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
547 548 config.add_route(name='repo_summary', pattern='/{repo_name}')
548 549 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
549 550 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
550 551
551 552 config.add_route(name='pullrequest_show',
552 553 pattern='/{repo_name}/pull-request/{pull_request_id}')
553 554 config.add_route(name='pull_requests_global',
554 555 pattern='/pull-request/{pull_request_id}')
555 556
556 557 config.add_route(name='repo_commit',
557 558 pattern='/{repo_name}/changeset/{commit_id}')
558 559 config.add_route(name='repo_files',
559 560 pattern='/{repo_name}/files/{commit_id}/{f_path}')
560 561
561 562 config.add_route(name='hovercard_user',
562 563 pattern='/_hovercard/user/{user_id}')
563 564
564 565 config.add_route(name='hovercard_user_group',
565 566 pattern='/_hovercard/user_group/{user_group_id}')
566 567
567 568 config.add_route(name='hovercard_pull_request',
568 569 pattern='/_hovercard/pull_request/{pull_request_id}')
569 570
570 571 config.add_route(name='hovercard_repo_commit',
571 572 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
572 573
573 574
574 575 def bootstrap_config(request):
575 576 import pyramid.testing
576 577 registry = pyramid.testing.Registry('RcTestRegistry')
577 578
578 579 config = pyramid.testing.setUp(registry=registry, request=request)
579 580
580 581 # allow pyramid lookup in testing
581 582 config.include('pyramid_mako')
582 583 config.include('rhodecode.lib.rc_beaker')
583 584 config.include('rhodecode.lib.rc_cache')
584 585
585 586 add_events_routes(config)
586 587
587 588 return config
588 589
589 590
590 591 def bootstrap_request(**kwargs):
591 592 import pyramid.testing
592 593
593 594 class TestRequest(pyramid.testing.DummyRequest):
594 595 application_url = kwargs.pop('application_url', 'http://example.com')
595 596 host = kwargs.pop('host', 'example.com:80')
596 597 domain = kwargs.pop('domain', 'example.com')
597 598
598 599 def translate(self, msg):
599 600 return msg
600 601
601 602 def plularize(self, singular, plural, n):
602 603 return singular
603 604
604 605 def get_partial_renderer(self, tmpl_name):
605 606
606 607 from rhodecode.lib.partial_renderer import get_partial_renderer
607 608 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
608 609
609 610 _call_context = TemplateArgs()
610 611 _call_context.visual = TemplateArgs()
611 612 _call_context.visual.show_sha_length = 12
612 613 _call_context.visual.show_revision_number = True
613 614
614 615 @property
615 616 def call_context(self):
616 617 return self._call_context
617 618
618 619 class TestDummySession(pyramid.testing.DummySession):
619 620 def save(*arg, **kw):
620 621 pass
621 622
622 623 request = TestRequest(**kwargs)
623 624 request.session = TestDummySession()
624 625
625 626 return request
626 627
@@ -1,379 +1,403 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode task modules, containing all task that suppose to be run
23 23 by celery daemon
24 24 """
25 25
26 26 import os
27 27 import time
28 28
29 29 from pyramid import compat
30 30 from pyramid_mailer.mailer import Mailer
31 31 from pyramid_mailer.message import Message
32 32 from email.utils import formatdate
33 33
34 34 import rhodecode
35 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 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 39 from rhodecode.model.db import (
40 40 Session, IntegrityError, true, Repository, RepoGroup, User)
41 41
42 42
43 43 @async_task(ignore_result=True, base=RequestContextTask)
44 44 def send_email(recipients, subject, body='', html_body='', email_config=None,
45 45 extra_headers=None):
46 46 """
47 47 Sends an email with defined parameters from the .ini files.
48 48
49 49 :param recipients: list of recipients, it this is empty the defined email
50 50 address from field 'email_to' is used instead
51 51 :param subject: subject of the mail
52 52 :param body: body of the mail
53 53 :param html_body: html version of body
54 54 :param email_config: specify custom configuration for mailer
55 55 :param extra_headers: specify custom headers
56 56 """
57 57 log = get_logger(send_email)
58 58
59 59 email_config = email_config or rhodecode.CONFIG
60 60
61 61 mail_server = email_config.get('smtp_server') or None
62 62 if mail_server is None:
63 63 log.error("SMTP server information missing. Sending email failed. "
64 64 "Make sure that `smtp_server` variable is configured "
65 65 "inside the .ini file")
66 66 return False
67 67
68 68 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
69 69
70 70 if recipients:
71 71 if isinstance(recipients, compat.string_types):
72 72 recipients = recipients.split(',')
73 73 else:
74 74 # if recipients are not defined we send to email_config + all admins
75 75 admins = []
76 76 for u in User.query().filter(User.admin == true()).all():
77 77 if u.email:
78 78 admins.append(u.email)
79 79 recipients = []
80 80 config_email = email_config.get('email_to')
81 81 if config_email:
82 82 recipients += [config_email]
83 83 recipients += admins
84 84
85 85 # translate our LEGACY config into the one that pyramid_mailer supports
86 86 email_conf = dict(
87 87 host=mail_server,
88 88 port=email_config.get('smtp_port', 25),
89 89 username=email_config.get('smtp_username'),
90 90 password=email_config.get('smtp_password'),
91 91
92 92 tls=str2bool(email_config.get('smtp_use_tls')),
93 93 ssl=str2bool(email_config.get('smtp_use_ssl')),
94 94
95 95 # SSL key file
96 96 # keyfile='',
97 97
98 98 # SSL certificate file
99 99 # certfile='',
100 100
101 101 # Location of maildir
102 102 # queue_path='',
103 103
104 104 default_sender=email_config.get('app_email_from', 'RhodeCode'),
105 105
106 106 debug=str2bool(email_config.get('smtp_debug')),
107 107 # /usr/sbin/sendmail Sendmail executable
108 108 # sendmail_app='',
109 109
110 110 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
111 111 # sendmail_template='',
112 112 )
113 113
114 114 if extra_headers is None:
115 115 extra_headers = {}
116 116
117 117 extra_headers.setdefault('Date', formatdate(time.time()))
118 118
119 119 if 'thread_ids' in extra_headers:
120 120 thread_ids = extra_headers.pop('thread_ids')
121 121 extra_headers['References'] = ' '.join('<{}>'.format(t) for t in thread_ids)
122 122
123 123 try:
124 124 mailer = Mailer(**email_conf)
125 125
126 126 message = Message(subject=subject,
127 127 sender=email_conf['default_sender'],
128 128 recipients=recipients,
129 129 body=body, html=html_body,
130 130 extra_headers=extra_headers)
131 131 mailer.send_immediately(message)
132 132
133 133 except Exception:
134 134 log.exception('Mail sending failed')
135 135 return False
136 136 return True
137 137
138 138
139 139 @async_task(ignore_result=True, base=RequestContextTask)
140 140 def create_repo(form_data, cur_user):
141 141 from rhodecode.model.repo import RepoModel
142 142 from rhodecode.model.user import UserModel
143 143 from rhodecode.model.scm import ScmModel
144 144 from rhodecode.model.settings import SettingsModel
145 145
146 146 log = get_logger(create_repo)
147 147
148 148 cur_user = UserModel()._get_user(cur_user)
149 149 owner = cur_user
150 150
151 151 repo_name = form_data['repo_name']
152 152 repo_name_full = form_data['repo_name_full']
153 153 repo_type = form_data['repo_type']
154 154 description = form_data['repo_description']
155 155 private = form_data['repo_private']
156 156 clone_uri = form_data.get('clone_uri')
157 157 repo_group = safe_int(form_data['repo_group'])
158 158 copy_fork_permissions = form_data.get('copy_permissions')
159 159 copy_group_permissions = form_data.get('repo_copy_permissions')
160 160 fork_of = form_data.get('fork_parent_id')
161 161 state = form_data.get('repo_state', Repository.STATE_PENDING)
162 162
163 163 # repo creation defaults, private and repo_type are filled in form
164 164 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
165 165 enable_statistics = form_data.get(
166 166 'enable_statistics', defs.get('repo_enable_statistics'))
167 167 enable_locking = form_data.get(
168 168 'enable_locking', defs.get('repo_enable_locking'))
169 169 enable_downloads = form_data.get(
170 170 'enable_downloads', defs.get('repo_enable_downloads'))
171 171
172 172 # set landing rev based on default branches for SCM
173 173 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
174 174
175 175 try:
176 176 RepoModel()._create_repo(
177 177 repo_name=repo_name_full,
178 178 repo_type=repo_type,
179 179 description=description,
180 180 owner=owner,
181 181 private=private,
182 182 clone_uri=clone_uri,
183 183 repo_group=repo_group,
184 184 landing_rev=landing_ref,
185 185 fork_of=fork_of,
186 186 copy_fork_permissions=copy_fork_permissions,
187 187 copy_group_permissions=copy_group_permissions,
188 188 enable_statistics=enable_statistics,
189 189 enable_locking=enable_locking,
190 190 enable_downloads=enable_downloads,
191 191 state=state
192 192 )
193 193 Session().commit()
194 194
195 195 # now create this repo on Filesystem
196 196 RepoModel()._create_filesystem_repo(
197 197 repo_name=repo_name,
198 198 repo_type=repo_type,
199 199 repo_group=RepoModel()._get_repo_group(repo_group),
200 200 clone_uri=clone_uri,
201 201 )
202 202 repo = Repository.get_by_repo_name(repo_name_full)
203 203 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
204 204
205 205 # update repo commit caches initially
206 206 repo.update_commit_cache()
207 207
208 208 # set new created state
209 209 repo.set_state(Repository.STATE_CREATED)
210 210 repo_id = repo.repo_id
211 211 repo_data = repo.get_api_data()
212 212
213 213 audit_logger.store(
214 214 'repo.create', action_data={'data': repo_data},
215 215 user=cur_user,
216 216 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
217 217
218 218 Session().commit()
219 219 except Exception as e:
220 220 log.warning('Exception occurred when creating repository, '
221 221 'doing cleanup...', exc_info=True)
222 222 if isinstance(e, IntegrityError):
223 223 Session().rollback()
224 224
225 225 # rollback things manually !
226 226 repo = Repository.get_by_repo_name(repo_name_full)
227 227 if repo:
228 228 Repository.delete(repo.repo_id)
229 229 Session().commit()
230 230 RepoModel()._delete_filesystem_repo(repo)
231 231 log.info('Cleanup of repo %s finished', repo_name_full)
232 232 raise
233 233
234 234 return True
235 235
236 236
237 237 @async_task(ignore_result=True, base=RequestContextTask)
238 238 def create_repo_fork(form_data, cur_user):
239 239 """
240 240 Creates a fork of repository using internal VCS methods
241 241 """
242 242 from rhodecode.model.repo import RepoModel
243 243 from rhodecode.model.user import UserModel
244 244
245 245 log = get_logger(create_repo_fork)
246 246
247 247 cur_user = UserModel()._get_user(cur_user)
248 248 owner = cur_user
249 249
250 250 repo_name = form_data['repo_name'] # fork in this case
251 251 repo_name_full = form_data['repo_name_full']
252 252 repo_type = form_data['repo_type']
253 253 description = form_data['description']
254 254 private = form_data['private']
255 255 clone_uri = form_data.get('clone_uri')
256 256 repo_group = safe_int(form_data['repo_group'])
257 257 landing_ref = form_data['landing_rev']
258 258 copy_fork_permissions = form_data.get('copy_permissions')
259 259 fork_id = safe_int(form_data.get('fork_parent_id'))
260 260
261 261 try:
262 262 fork_of = RepoModel()._get_repo(fork_id)
263 263 RepoModel()._create_repo(
264 264 repo_name=repo_name_full,
265 265 repo_type=repo_type,
266 266 description=description,
267 267 owner=owner,
268 268 private=private,
269 269 clone_uri=clone_uri,
270 270 repo_group=repo_group,
271 271 landing_rev=landing_ref,
272 272 fork_of=fork_of,
273 273 copy_fork_permissions=copy_fork_permissions
274 274 )
275 275
276 276 Session().commit()
277 277
278 278 base_path = Repository.base_path()
279 279 source_repo_path = os.path.join(base_path, fork_of.repo_name)
280 280
281 281 # now create this repo on Filesystem
282 282 RepoModel()._create_filesystem_repo(
283 283 repo_name=repo_name,
284 284 repo_type=repo_type,
285 285 repo_group=RepoModel()._get_repo_group(repo_group),
286 286 clone_uri=source_repo_path,
287 287 )
288 288 repo = Repository.get_by_repo_name(repo_name_full)
289 289 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
290 290
291 291 # update repo commit caches initially
292 292 config = repo._config
293 293 config.set('extensions', 'largefiles', '')
294 294 repo.update_commit_cache(config=config)
295 295
296 296 # set new created state
297 297 repo.set_state(Repository.STATE_CREATED)
298 298
299 299 repo_id = repo.repo_id
300 300 repo_data = repo.get_api_data()
301 301 audit_logger.store(
302 302 'repo.fork', action_data={'data': repo_data},
303 303 user=cur_user,
304 304 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
305 305
306 306 Session().commit()
307 307 except Exception as e:
308 308 log.warning('Exception occurred when forking repository, '
309 309 'doing cleanup...', exc_info=True)
310 310 if isinstance(e, IntegrityError):
311 311 Session().rollback()
312 312
313 313 # rollback things manually !
314 314 repo = Repository.get_by_repo_name(repo_name_full)
315 315 if repo:
316 316 Repository.delete(repo.repo_id)
317 317 Session().commit()
318 318 RepoModel()._delete_filesystem_repo(repo)
319 319 log.info('Cleanup of repo %s finished', repo_name_full)
320 320 raise
321 321
322 322 return True
323 323
324 324
325 325 @async_task(ignore_result=True)
326 326 def repo_maintenance(repoid):
327 327 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
328 328 log = get_logger(repo_maintenance)
329 329 repo = Repository.get_by_id_or_repo_name(repoid)
330 330 if repo:
331 331 maintenance = repo_maintenance_lib.RepoMaintenance()
332 332 tasks = maintenance.get_tasks_for_repo(repo)
333 333 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
334 334 executed_types = maintenance.execute(repo)
335 335 log.debug('Got execution results %s', executed_types)
336 336 else:
337 337 log.debug('Repo `%s` not found or without a clone_url', repoid)
338 338
339 339
340 340 @async_task(ignore_result=True)
341 def check_for_update():
341 def check_for_update(send_email_notification=True, email_recipients=None):
342 342 from rhodecode.model.update import UpdateModel
343 from rhodecode.model.notification import EmailNotificationModel
344
345 log = get_logger(check_for_update)
343 346 update_url = UpdateModel().get_update_url()
344 347 cur_ver = rhodecode.__version__
345 348
346 349 try:
347 350 data = UpdateModel().get_update_data(update_url)
348 latest = data['versions'][0]
349 UpdateModel().store_version(latest['version'])
351
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 [user.email for user in User.get_all_super_admins()]
371 run_task(send_email, email_recipients, subject,
372 email_body_plaintext, email_body)
373
350 374 except Exception:
351 375 pass
352 376
353 377
354 378 @async_task(ignore_result=False)
355 379 def beat_check(*args, **kwargs):
356 380 log = get_logger(beat_check)
357 381 log.info('%r: Got args: %r and kwargs %r', beat_check, args, kwargs)
358 382 return time.time()
359 383
360 384
361 385 @async_task(ignore_result=True)
362 386 def sync_last_update(*args, **kwargs):
363 387
364 388 skip_repos = kwargs.get('skip_repos')
365 389 if not skip_repos:
366 390 repos = Repository.query() \
367 391 .order_by(Repository.group_id.asc())
368 392
369 393 for repo in repos:
370 394 repo.update_commit_cache()
371 395
372 396 skip_groups = kwargs.get('skip_groups')
373 397 if not skip_groups:
374 398 repo_groups = RepoGroup.query() \
375 399 .filter(RepoGroup.group_parent_id == None)
376 400
377 401 for root_gr in repo_groups:
378 402 for repo_gr in reversed(root_gr.recursive_groups()):
379 403 repo_gr.update_commit_cache()
@@ -1,678 +1,680 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args=None):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.cli_args = cli_args or {}
70 70 self.init_db(SESSION=SESSION)
71 71 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72 72
73 73 def db_exists(self):
74 74 if not self.sa:
75 75 self.init_db()
76 76 try:
77 77 self.sa.query(RhodeCodeUi)\
78 78 .filter(RhodeCodeUi.ui_key == '/')\
79 79 .scalar()
80 80 return True
81 81 except Exception:
82 82 return False
83 83 finally:
84 84 self.sa.rollback()
85 85
86 86 def get_ask_ok_func(self, param):
87 87 if param not in [None]:
88 88 # return a function lambda that has a default set to param
89 89 return lambda *args, **kwargs: param
90 90 else:
91 91 from rhodecode.lib.utils import ask_ok
92 92 return ask_ok
93 93
94 94 def init_db(self, SESSION=None):
95 95 if SESSION:
96 96 self.sa = SESSION
97 97 else:
98 98 # init new sessions
99 99 engine = create_engine(self.dburi, echo=self.log_sql)
100 100 init_model(engine)
101 101 self.sa = Session()
102 102
103 103 def create_tables(self, override=False):
104 104 """
105 105 Create a auth database
106 106 """
107 107
108 108 log.info("Existing database with the same name is going to be destroyed.")
109 109 log.info("Setup command will run DROP ALL command on that database.")
110 110 if self.tests:
111 111 destroy = True
112 112 else:
113 113 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
114 114 if not destroy:
115 115 log.info('Nothing done.')
116 116 sys.exit(0)
117 117 if destroy:
118 118 Base.metadata.drop_all()
119 119
120 120 checkfirst = not override
121 121 Base.metadata.create_all(checkfirst=checkfirst)
122 122 log.info('Created tables for %s', self.dbname)
123 123
124 124 def set_db_version(self):
125 125 ver = DbMigrateVersion()
126 126 ver.version = __dbversion__
127 127 ver.repository_id = 'rhodecode_db_migrations'
128 128 ver.repository_path = 'versions'
129 129 self.sa.add(ver)
130 130 log.info('db version set to: %s', __dbversion__)
131 131
132 132 def run_post_migration_tasks(self):
133 133 """
134 134 Run various tasks before actually doing migrations
135 135 """
136 136 # delete cache keys on each upgrade
137 137 total = CacheKey.query().count()
138 138 log.info("Deleting (%s) cache keys now...", total)
139 139 CacheKey.delete_all_cache()
140 140
141 141 def upgrade(self, version=None):
142 142 """
143 143 Upgrades given database schema to given revision following
144 144 all needed steps, to perform the upgrade
145 145
146 146 """
147 147
148 148 from rhodecode.lib.dbmigrate.migrate.versioning import api
149 149 from rhodecode.lib.dbmigrate.migrate.exceptions import \
150 150 DatabaseNotControlledError
151 151
152 152 if 'sqlite' in self.dburi:
153 153 print(
154 154 '********************** WARNING **********************\n'
155 155 'Make sure your version of sqlite is at least 3.7.X. \n'
156 156 'Earlier versions are known to fail on some migrations\n'
157 157 '*****************************************************\n')
158 158
159 159 upgrade = self.ask_ok(
160 160 'You are about to perform a database upgrade. Make '
161 161 'sure you have backed up your database. '
162 162 'Continue ? [y/n]')
163 163 if not upgrade:
164 164 log.info('No upgrade performed')
165 165 sys.exit(0)
166 166
167 167 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
168 168 'rhodecode/lib/dbmigrate')
169 169 db_uri = self.dburi
170 170
171 171 if version:
172 172 DbMigrateVersion.set_version(version)
173 173
174 174 try:
175 175 curr_version = api.db_version(db_uri, repository_path)
176 176 msg = ('Found current database db_uri under version '
177 177 'control with version {}'.format(curr_version))
178 178
179 179 except (RuntimeError, DatabaseNotControlledError):
180 180 curr_version = 1
181 181 msg = ('Current database is not under version control. Setting '
182 182 'as version %s' % curr_version)
183 183 api.version_control(db_uri, repository_path, curr_version)
184 184
185 185 notify(msg)
186 186
187 187
188 188 if curr_version == __dbversion__:
189 189 log.info('This database is already at the newest version')
190 190 sys.exit(0)
191 191
192 192 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
193 193 notify('attempting to upgrade database from '
194 194 'version %s to version %s' % (curr_version, __dbversion__))
195 195
196 196 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
197 197 _step = None
198 198 for step in upgrade_steps:
199 199 notify('performing upgrade step %s' % step)
200 200 time.sleep(0.5)
201 201
202 202 api.upgrade(db_uri, repository_path, step)
203 203 self.sa.rollback()
204 204 notify('schema upgrade for step %s completed' % (step,))
205 205
206 206 _step = step
207 207
208 208 self.run_post_migration_tasks()
209 209 notify('upgrade to version %s successful' % _step)
210 210
211 211 def fix_repo_paths(self):
212 212 """
213 213 Fixes an old RhodeCode version path into new one without a '*'
214 214 """
215 215
216 216 paths = self.sa.query(RhodeCodeUi)\
217 217 .filter(RhodeCodeUi.ui_key == '/')\
218 218 .scalar()
219 219
220 220 paths.ui_value = paths.ui_value.replace('*', '')
221 221
222 222 try:
223 223 self.sa.add(paths)
224 224 self.sa.commit()
225 225 except Exception:
226 226 self.sa.rollback()
227 227 raise
228 228
229 229 def fix_default_user(self):
230 230 """
231 231 Fixes an old default user with some 'nicer' default values,
232 232 used mostly for anonymous access
233 233 """
234 234 def_user = self.sa.query(User)\
235 235 .filter(User.username == User.DEFAULT_USER)\
236 236 .one()
237 237
238 238 def_user.name = 'Anonymous'
239 239 def_user.lastname = 'User'
240 240 def_user.email = User.DEFAULT_USER_EMAIL
241 241
242 242 try:
243 243 self.sa.add(def_user)
244 244 self.sa.commit()
245 245 except Exception:
246 246 self.sa.rollback()
247 247 raise
248 248
249 249 def fix_settings(self):
250 250 """
251 251 Fixes rhodecode settings and adds ga_code key for google analytics
252 252 """
253 253
254 254 hgsettings3 = RhodeCodeSetting('ga_code', '')
255 255
256 256 try:
257 257 self.sa.add(hgsettings3)
258 258 self.sa.commit()
259 259 except Exception:
260 260 self.sa.rollback()
261 261 raise
262 262
263 263 def create_admin_and_prompt(self):
264 264
265 265 # defaults
266 266 defaults = self.cli_args
267 267 username = defaults.get('username')
268 268 password = defaults.get('password')
269 269 email = defaults.get('email')
270 270
271 271 if username is None:
272 272 username = raw_input('Specify admin username:')
273 273 if password is None:
274 274 password = self._get_admin_password()
275 275 if not password:
276 276 # second try
277 277 password = self._get_admin_password()
278 278 if not password:
279 279 sys.exit()
280 280 if email is None:
281 281 email = raw_input('Specify admin email:')
282 282 api_key = self.cli_args.get('api_key')
283 283 self.create_user(username, password, email, True,
284 284 strict_creation_check=False,
285 285 api_key=api_key)
286 286
287 287 def _get_admin_password(self):
288 288 password = getpass.getpass('Specify admin password '
289 289 '(min 6 chars):')
290 290 confirm = getpass.getpass('Confirm password:')
291 291
292 292 if password != confirm:
293 293 log.error('passwords mismatch')
294 294 return False
295 295 if len(password) < 6:
296 296 log.error('password is too short - use at least 6 characters')
297 297 return False
298 298
299 299 return password
300 300
301 301 def create_test_admin_and_users(self):
302 302 log.info('creating admin and regular test users')
303 303 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
304 304 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
305 305 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
306 306 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
307 307 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
308 308
309 309 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
310 310 TEST_USER_ADMIN_EMAIL, True, api_key=True)
311 311
312 312 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
313 313 TEST_USER_REGULAR_EMAIL, False, api_key=True)
314 314
315 315 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
316 316 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
317 317
318 318 def create_ui_settings(self, repo_store_path):
319 319 """
320 320 Creates ui settings, fills out hooks
321 321 and disables dotencode
322 322 """
323 323 settings_model = SettingsModel(sa=self.sa)
324 324 from rhodecode.lib.vcs.backends.hg import largefiles_store
325 325 from rhodecode.lib.vcs.backends.git import lfs_store
326 326
327 327 # Build HOOKS
328 328 hooks = [
329 329 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
330 330
331 331 # HG
332 332 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
333 333 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
334 334 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
335 335 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
336 336 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
337 337 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
338 338
339 339 ]
340 340
341 341 for key, value in hooks:
342 342 hook_obj = settings_model.get_ui_by_key(key)
343 343 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
344 344 hooks2.ui_section = 'hooks'
345 345 hooks2.ui_key = key
346 346 hooks2.ui_value = value
347 347 self.sa.add(hooks2)
348 348
349 349 # enable largefiles
350 350 largefiles = RhodeCodeUi()
351 351 largefiles.ui_section = 'extensions'
352 352 largefiles.ui_key = 'largefiles'
353 353 largefiles.ui_value = ''
354 354 self.sa.add(largefiles)
355 355
356 356 # set default largefiles cache dir, defaults to
357 357 # /repo_store_location/.cache/largefiles
358 358 largefiles = RhodeCodeUi()
359 359 largefiles.ui_section = 'largefiles'
360 360 largefiles.ui_key = 'usercache'
361 361 largefiles.ui_value = largefiles_store(repo_store_path)
362 362
363 363 self.sa.add(largefiles)
364 364
365 365 # set default lfs cache dir, defaults to
366 366 # /repo_store_location/.cache/lfs_store
367 367 lfsstore = RhodeCodeUi()
368 368 lfsstore.ui_section = 'vcs_git_lfs'
369 369 lfsstore.ui_key = 'store_location'
370 370 lfsstore.ui_value = lfs_store(repo_store_path)
371 371
372 372 self.sa.add(lfsstore)
373 373
374 374 # enable hgsubversion disabled by default
375 375 hgsubversion = RhodeCodeUi()
376 376 hgsubversion.ui_section = 'extensions'
377 377 hgsubversion.ui_key = 'hgsubversion'
378 378 hgsubversion.ui_value = ''
379 379 hgsubversion.ui_active = False
380 380 self.sa.add(hgsubversion)
381 381
382 382 # enable hgevolve disabled by default
383 383 hgevolve = RhodeCodeUi()
384 384 hgevolve.ui_section = 'extensions'
385 385 hgevolve.ui_key = 'evolve'
386 386 hgevolve.ui_value = ''
387 387 hgevolve.ui_active = False
388 388 self.sa.add(hgevolve)
389 389
390 390 hgevolve = RhodeCodeUi()
391 391 hgevolve.ui_section = 'experimental'
392 392 hgevolve.ui_key = 'evolution'
393 393 hgevolve.ui_value = ''
394 394 hgevolve.ui_active = False
395 395 self.sa.add(hgevolve)
396 396
397 397 hgevolve = RhodeCodeUi()
398 398 hgevolve.ui_section = 'experimental'
399 399 hgevolve.ui_key = 'evolution.exchange'
400 400 hgevolve.ui_value = ''
401 401 hgevolve.ui_active = False
402 402 self.sa.add(hgevolve)
403 403
404 404 hgevolve = RhodeCodeUi()
405 405 hgevolve.ui_section = 'extensions'
406 406 hgevolve.ui_key = 'topic'
407 407 hgevolve.ui_value = ''
408 408 hgevolve.ui_active = False
409 409 self.sa.add(hgevolve)
410 410
411 411 # enable hggit disabled by default
412 412 hggit = RhodeCodeUi()
413 413 hggit.ui_section = 'extensions'
414 414 hggit.ui_key = 'hggit'
415 415 hggit.ui_value = ''
416 416 hggit.ui_active = False
417 417 self.sa.add(hggit)
418 418
419 419 # set svn branch defaults
420 420 branches = ["/branches/*", "/trunk"]
421 421 tags = ["/tags/*"]
422 422
423 423 for branch in branches:
424 424 settings_model.create_ui_section_value(
425 425 RhodeCodeUi.SVN_BRANCH_ID, branch)
426 426
427 427 for tag in tags:
428 428 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
429 429
430 430 def create_auth_plugin_options(self, skip_existing=False):
431 431 """
432 432 Create default auth plugin settings, and make it active
433 433
434 434 :param skip_existing:
435 435 """
436 436 defaults = [
437 437 ('auth_plugins',
438 438 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
439 439 'list'),
440 440
441 441 ('auth_authtoken_enabled',
442 442 'True',
443 443 'bool'),
444 444
445 445 ('auth_rhodecode_enabled',
446 446 'True',
447 447 'bool'),
448 448 ]
449 449 for k, v, t in defaults:
450 450 if (skip_existing and
451 451 SettingsModel().get_setting_by_name(k) is not None):
452 452 log.debug('Skipping option %s', k)
453 453 continue
454 454 setting = RhodeCodeSetting(k, v, t)
455 455 self.sa.add(setting)
456 456
457 457 def create_default_options(self, skip_existing=False):
458 458 """Creates default settings"""
459 459
460 460 for k, v, t in [
461 461 ('default_repo_enable_locking', False, 'bool'),
462 462 ('default_repo_enable_downloads', False, 'bool'),
463 463 ('default_repo_enable_statistics', False, 'bool'),
464 464 ('default_repo_private', False, 'bool'),
465 465 ('default_repo_type', 'hg', 'unicode')]:
466 466
467 467 if (skip_existing and
468 468 SettingsModel().get_setting_by_name(k) is not None):
469 469 log.debug('Skipping option %s', k)
470 470 continue
471 471 setting = RhodeCodeSetting(k, v, t)
472 472 self.sa.add(setting)
473 473
474 474 def fixup_groups(self):
475 475 def_usr = User.get_default_user()
476 476 for g in RepoGroup.query().all():
477 477 g.group_name = g.get_new_name(g.name)
478 478 self.sa.add(g)
479 479 # get default perm
480 480 default = UserRepoGroupToPerm.query()\
481 481 .filter(UserRepoGroupToPerm.group == g)\
482 482 .filter(UserRepoGroupToPerm.user == def_usr)\
483 483 .scalar()
484 484
485 485 if default is None:
486 486 log.debug('missing default permission for group %s adding', g)
487 487 perm_obj = RepoGroupModel()._create_default_perms(g)
488 488 self.sa.add(perm_obj)
489 489
490 490 def reset_permissions(self, username):
491 491 """
492 492 Resets permissions to default state, useful when old systems had
493 493 bad permissions, we must clean them up
494 494
495 495 :param username:
496 496 """
497 497 default_user = User.get_by_username(username)
498 498 if not default_user:
499 499 return
500 500
501 501 u2p = UserToPerm.query()\
502 502 .filter(UserToPerm.user == default_user).all()
503 503 fixed = False
504 504 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
505 505 for p in u2p:
506 506 Session().delete(p)
507 507 fixed = True
508 508 self.populate_default_permissions()
509 509 return fixed
510 510
511 511 def config_prompt(self, test_repo_path='', retries=3):
512 512 defaults = self.cli_args
513 513 _path = defaults.get('repos_location')
514 514 if retries == 3:
515 515 log.info('Setting up repositories config')
516 516
517 517 if _path is not None:
518 518 path = _path
519 519 elif not self.tests and not test_repo_path:
520 520 path = raw_input(
521 521 'Enter a valid absolute path to store repositories. '
522 522 'All repositories in that path will be added automatically:'
523 523 )
524 524 else:
525 525 path = test_repo_path
526 526 path_ok = True
527 527
528 528 # check proper dir
529 529 if not os.path.isdir(path):
530 530 path_ok = False
531 531 log.error('Given path %s is not a valid directory', path)
532 532
533 533 elif not os.path.isabs(path):
534 534 path_ok = False
535 535 log.error('Given path %s is not an absolute path', path)
536 536
537 537 # check if path is at least readable.
538 538 if not os.access(path, os.R_OK):
539 539 path_ok = False
540 540 log.error('Given path %s is not readable', path)
541 541
542 542 # check write access, warn user about non writeable paths
543 543 elif not os.access(path, os.W_OK) and path_ok:
544 544 log.warning('No write permission to given path %s', path)
545 545
546 546 q = ('Given path %s is not writeable, do you want to '
547 547 'continue with read only mode ? [y/n]' % (path,))
548 548 if not self.ask_ok(q):
549 549 log.error('Canceled by user')
550 550 sys.exit(-1)
551 551
552 552 if retries == 0:
553 553 sys.exit('max retries reached')
554 554 if not path_ok:
555 555 retries -= 1
556 556 return self.config_prompt(test_repo_path, retries)
557 557
558 558 real_path = os.path.normpath(os.path.realpath(path))
559 559
560 560 if real_path != os.path.normpath(path):
561 561 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
562 562 'given path as %s ? [y/n]') % (real_path,)
563 563 if not self.ask_ok(q):
564 564 log.error('Canceled by user')
565 565 sys.exit(-1)
566 566
567 567 return real_path
568 568
569 569 def create_settings(self, path):
570 570
571 571 self.create_ui_settings(path)
572 572
573 573 ui_config = [
574 574 ('web', 'push_ssl', 'False'),
575 575 ('web', 'allow_archive', 'gz zip bz2'),
576 576 ('web', 'allow_push', '*'),
577 577 ('web', 'baseurl', '/'),
578 578 ('paths', '/', path),
579 579 ('phases', 'publish', 'True')
580 580 ]
581 581 for section, key, value in ui_config:
582 582 ui_conf = RhodeCodeUi()
583 583 setattr(ui_conf, 'ui_section', section)
584 584 setattr(ui_conf, 'ui_key', key)
585 585 setattr(ui_conf, 'ui_value', value)
586 586 self.sa.add(ui_conf)
587 587
588 588 # rhodecode app settings
589 589 settings = [
590 590 ('realm', 'RhodeCode', 'unicode'),
591 591 ('title', '', 'unicode'),
592 592 ('pre_code', '', 'unicode'),
593 593 ('post_code', '', 'unicode'),
594 594
595 595 # Visual
596 596 ('show_public_icon', True, 'bool'),
597 597 ('show_private_icon', True, 'bool'),
598 ('stylify_metatags', False, 'bool'),
598 ('stylify_metatags', True, 'bool'),
599 599 ('dashboard_items', 100, 'int'),
600 600 ('admin_grid_items', 25, 'int'),
601 601
602 602 ('markup_renderer', 'markdown', 'unicode'),
603 603
604 ('repository_fields', True, 'bool'),
604 605 ('show_version', True, 'bool'),
605 606 ('show_revision_number', True, 'bool'),
606 607 ('show_sha_length', 12, 'int'),
607 608
608 609 ('use_gravatar', False, 'bool'),
609 610 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
610 611
611 612 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
613 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
612 614 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
613 615 ('support_url', '', 'unicode'),
614 616 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
615 617
616 618 # VCS Settings
617 619 ('pr_merge_enabled', True, 'bool'),
618 620 ('use_outdated_comments', True, 'bool'),
619 621 ('diff_cache', True, 'bool'),
620 622 ]
621 623
622 624 for key, val, type_ in settings:
623 625 sett = RhodeCodeSetting(key, val, type_)
624 626 self.sa.add(sett)
625 627
626 628 self.create_auth_plugin_options()
627 629 self.create_default_options()
628 630
629 631 log.info('created ui config')
630 632
631 633 def create_user(self, username, password, email='', admin=False,
632 634 strict_creation_check=True, api_key=None):
633 635 log.info('creating user `%s`', username)
634 636 user = UserModel().create_or_update(
635 637 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
636 638 active=True, admin=admin, extern_type="rhodecode",
637 639 strict_creation_check=strict_creation_check)
638 640
639 641 if api_key:
640 642 log.info('setting a new default auth token for user `%s`', username)
641 643 UserModel().add_auth_token(
642 644 user=user, lifetime_minutes=-1,
643 645 role=UserModel.auth_token_role.ROLE_ALL,
644 646 description=u'BUILTIN TOKEN')
645 647
646 648 def create_default_user(self):
647 649 log.info('creating default user')
648 650 # create default user for handling default permissions.
649 651 user = UserModel().create_or_update(username=User.DEFAULT_USER,
650 652 password=str(uuid.uuid1())[:20],
651 653 email=User.DEFAULT_USER_EMAIL,
652 654 firstname=u'Anonymous',
653 655 lastname=u'User',
654 656 strict_creation_check=False)
655 657 # based on configuration options activate/de-activate this user which
656 658 # controlls anonymous access
657 659 if self.cli_args.get('public_access') is False:
658 660 log.info('Public access disabled')
659 661 user.active = False
660 662 Session().add(user)
661 663 Session().commit()
662 664
663 665 def create_permissions(self):
664 666 """
665 667 Creates all permissions defined in the system
666 668 """
667 669 # module.(access|create|change|delete)_[name]
668 670 # module.(none|read|write|admin)
669 671 log.info('creating permissions')
670 672 PermissionModel(self.sa).create_permissions()
671 673
672 674 def populate_default_permissions(self):
673 675 """
674 676 Populate default permissions. It will create only the default
675 677 permissions that are missing, and not alter already defined ones
676 678 """
677 679 log.info('creating default user permissions')
678 680 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,65 +1,73 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23
24 24 import rhodecode
25 25 from rhodecode.lib.auth import AuthUser
26 26 from rhodecode.lib.base import get_ip_addr, get_access_path, get_user_agent
27 27 from rhodecode.lib.utils2 import safe_str, get_current_rhodecode_user
28 28
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 class RequestWrapperTween(object):
34 34 def __init__(self, handler, registry):
35 35 self.handler = handler
36 36 self.registry = registry
37 37
38 38 # one-time configuration code goes here
39 39
40 def _get_user_info(self, request):
41 user = get_current_rhodecode_user(request)
42 if not user:
43 user = AuthUser.repr_user(ip=get_ip_addr(request.environ))
44 return user
45
40 46 def __call__(self, request):
41 47 start = time.time()
42 48 log.debug('Starting request time measurement')
43 49 try:
44 50 response = self.handler(request)
45 51 finally:
46 end = time.time()
47 total = end - start
48 52 count = request.request_count()
49 53 _ver_ = rhodecode.__version__
50 default_user_info = AuthUser.repr_user(ip=get_ip_addr(request.environ))
51 user_info = get_current_rhodecode_user(request) or default_user_info
54 statsd = request.statsd
55 total = time.time() - start
56 if statsd:
57 statsd.timing('rhodecode.req.timing', total)
58 statsd.incr('rhodecode.req.count')
59
52 60 log.info(
53 61 'Req[%4s] %s %s Request to %s time: %.4fs [%s], RhodeCode %s',
54 count, user_info, request.environ.get('REQUEST_METHOD'),
62 count, self._get_user_info(request), request.environ.get('REQUEST_METHOD'),
55 63 safe_str(get_access_path(request.environ)), total,
56 64 get_user_agent(request. environ), _ver_
57 65 )
58 66
59 67 return response
60 68
61 69
62 70 def includeme(config):
63 71 config.add_tween(
64 72 'rhodecode.lib.middleware.request_wrapper.RequestWrapperTween',
65 73 )
@@ -1,996 +1,1012 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 HG repository module
23 23 """
24 24 import os
25 25 import logging
26 26 import binascii
27 27 import urllib
28 28
29 29 from zope.cachedescriptors.property import Lazy as LazyProperty
30 30
31 31 from rhodecode.lib.compat import OrderedDict
32 32 from rhodecode.lib.datelib import (
33 33 date_to_timestamp_plus_offset, utcdate_fromtimestamp, makedate)
34 34 from rhodecode.lib.utils import safe_unicode, safe_str
35 35 from rhodecode.lib.utils2 import CachedProperty
36 36 from rhodecode.lib.vcs import connection, exceptions
37 37 from rhodecode.lib.vcs.backends.base import (
38 38 BaseRepository, CollectionGenerator, Config, MergeResponse,
39 39 MergeFailureReason, Reference, BasePathPermissionChecker)
40 40 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
41 41 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
42 42 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
43 43 from rhodecode.lib.vcs.exceptions import (
44 44 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
45 45 TagDoesNotExistError, CommitDoesNotExistError, SubrepoMergeError, UnresolvedFilesInRepo)
46 46 from rhodecode.lib.vcs.compat import configparser
47 47
48 48 hexlify = binascii.hexlify
49 49 nullid = "\0" * 20
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class MercurialRepository(BaseRepository):
55 55 """
56 56 Mercurial repository backend
57 57 """
58 58 DEFAULT_BRANCH_NAME = 'default'
59 59
60 60 def __init__(self, repo_path, config=None, create=False, src_url=None,
61 61 do_workspace_checkout=False, with_wire=None, bare=False):
62 62 """
63 63 Raises RepositoryError if repository could not be find at the given
64 64 ``repo_path``.
65 65
66 66 :param repo_path: local path of the repository
67 67 :param config: config object containing the repo configuration
68 68 :param create=False: if set to True, would try to create repository if
69 69 it does not exist rather than raising exception
70 70 :param src_url=None: would try to clone repository from given location
71 71 :param do_workspace_checkout=False: sets update of working copy after
72 72 making a clone
73 73 :param bare: not used, compatible with other VCS
74 74 """
75 75
76 76 self.path = safe_str(os.path.abspath(repo_path))
77 77 # mercurial since 4.4.X requires certain configuration to be present
78 78 # because sometimes we init the repos with config we need to meet
79 79 # special requirements
80 80 self.config = config if config else self.get_default_config(
81 81 default=[('extensions', 'largefiles', '1')])
82 82 self.with_wire = with_wire or {"cache": False} # default should not use cache
83 83
84 84 self._init_repo(create, src_url, do_workspace_checkout)
85 85
86 86 # caches
87 87 self._commit_ids = {}
88 88
89 89 @LazyProperty
90 90 def _remote(self):
91 91 repo_id = self.path
92 92 return connection.Hg(self.path, repo_id, self.config, with_wire=self.with_wire)
93 93
94 94 @CachedProperty
95 95 def commit_ids(self):
96 96 """
97 97 Returns list of commit ids, in ascending order. Being lazy
98 98 attribute allows external tools to inject shas from cache.
99 99 """
100 100 commit_ids = self._get_all_commit_ids()
101 101 self._rebuild_cache(commit_ids)
102 102 return commit_ids
103 103
104 104 def _rebuild_cache(self, commit_ids):
105 105 self._commit_ids = dict((commit_id, index)
106 106 for index, commit_id in enumerate(commit_ids))
107 107
108 108 @CachedProperty
109 109 def branches(self):
110 110 return self._get_branches()
111 111
112 112 @CachedProperty
113 113 def branches_closed(self):
114 114 return self._get_branches(active=False, closed=True)
115 115
116 116 @CachedProperty
117 117 def branches_all(self):
118 118 all_branches = {}
119 119 all_branches.update(self.branches)
120 120 all_branches.update(self.branches_closed)
121 121 return all_branches
122 122
123 123 def _get_branches(self, active=True, closed=False):
124 124 """
125 125 Gets branches for this repository
126 126 Returns only not closed active branches by default
127 127
128 128 :param active: return also active branches
129 129 :param closed: return also closed branches
130 130
131 131 """
132 132 if self.is_empty():
133 133 return {}
134 134
135 135 def get_name(ctx):
136 136 return ctx[0]
137 137
138 138 _branches = [(safe_unicode(n), hexlify(h),) for n, h in
139 139 self._remote.branches(active, closed).items()]
140 140
141 141 return OrderedDict(sorted(_branches, key=get_name, reverse=False))
142 142
143 143 @CachedProperty
144 144 def tags(self):
145 145 """
146 146 Gets tags for this repository
147 147 """
148 148 return self._get_tags()
149 149
150 150 def _get_tags(self):
151 151 if self.is_empty():
152 152 return {}
153 153
154 154 def get_name(ctx):
155 155 return ctx[0]
156 156
157 157 _tags = [(safe_unicode(n), hexlify(h),) for n, h in
158 158 self._remote.tags().items()]
159 159
160 160 return OrderedDict(sorted(_tags, key=get_name, reverse=True))
161 161
162 162 def tag(self, name, user, commit_id=None, message=None, date=None, **kwargs):
163 163 """
164 164 Creates and returns a tag for the given ``commit_id``.
165 165
166 166 :param name: name for new tag
167 167 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
168 168 :param commit_id: commit id for which new tag would be created
169 169 :param message: message of the tag's commit
170 170 :param date: date of tag's commit
171 171
172 172 :raises TagAlreadyExistError: if tag with same name already exists
173 173 """
174 174 if name in self.tags:
175 175 raise TagAlreadyExistError("Tag %s already exists" % name)
176 176
177 177 commit = self.get_commit(commit_id=commit_id)
178 178 local = kwargs.setdefault('local', False)
179 179
180 180 if message is None:
181 181 message = "Added tag %s for commit %s" % (name, commit.short_id)
182 182
183 183 date, tz = date_to_timestamp_plus_offset(date)
184 184
185 185 self._remote.tag(name, commit.raw_id, message, local, user, date, tz)
186 186 self._remote.invalidate_vcs_cache()
187 187
188 188 # Reinitialize tags
189 189 self._invalidate_prop_cache('tags')
190 190 tag_id = self.tags[name]
191 191
192 192 return self.get_commit(commit_id=tag_id)
193 193
194 194 def remove_tag(self, name, user, message=None, date=None):
195 195 """
196 196 Removes tag with the given `name`.
197 197
198 198 :param name: name of the tag to be removed
199 199 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
200 200 :param message: message of the tag's removal commit
201 201 :param date: date of tag's removal commit
202 202
203 203 :raises TagDoesNotExistError: if tag with given name does not exists
204 204 """
205 205 if name not in self.tags:
206 206 raise TagDoesNotExistError("Tag %s does not exist" % name)
207 207
208 208 if message is None:
209 209 message = "Removed tag %s" % name
210 210 local = False
211 211
212 212 date, tz = date_to_timestamp_plus_offset(date)
213 213
214 214 self._remote.tag(name, nullid, message, local, user, date, tz)
215 215 self._remote.invalidate_vcs_cache()
216 216 self._invalidate_prop_cache('tags')
217 217
218 218 @LazyProperty
219 219 def bookmarks(self):
220 220 """
221 221 Gets bookmarks for this repository
222 222 """
223 223 return self._get_bookmarks()
224 224
225 225 def _get_bookmarks(self):
226 226 if self.is_empty():
227 227 return {}
228 228
229 229 def get_name(ctx):
230 230 return ctx[0]
231 231
232 232 _bookmarks = [
233 233 (safe_unicode(n), hexlify(h)) for n, h in
234 234 self._remote.bookmarks().items()]
235 235
236 236 return OrderedDict(sorted(_bookmarks, key=get_name))
237 237
238 238 def _get_all_commit_ids(self):
239 239 return self._remote.get_all_commit_ids('visible')
240 240
241 241 def get_diff(
242 242 self, commit1, commit2, path='', ignore_whitespace=False,
243 243 context=3, path1=None):
244 244 """
245 245 Returns (git like) *diff*, as plain text. Shows changes introduced by
246 246 `commit2` since `commit1`.
247 247
248 248 :param commit1: Entry point from which diff is shown. Can be
249 249 ``self.EMPTY_COMMIT`` - in this case, patch showing all
250 250 the changes since empty state of the repository until `commit2`
251 251 :param commit2: Until which commit changes should be shown.
252 252 :param ignore_whitespace: If set to ``True``, would not show whitespace
253 253 changes. Defaults to ``False``.
254 254 :param context: How many lines before/after changed lines should be
255 255 shown. Defaults to ``3``.
256 256 """
257 257 self._validate_diff_commits(commit1, commit2)
258 258 if path1 is not None and path1 != path:
259 259 raise ValueError("Diff of two different paths not supported.")
260 260
261 261 if path:
262 262 file_filter = [self.path, path]
263 263 else:
264 264 file_filter = None
265 265
266 266 diff = self._remote.diff(
267 267 commit1.raw_id, commit2.raw_id, file_filter=file_filter,
268 268 opt_git=True, opt_ignorews=ignore_whitespace,
269 269 context=context)
270 270 return MercurialDiff(diff)
271 271
272 272 def strip(self, commit_id, branch=None):
273 273 self._remote.strip(commit_id, update=False, backup="none")
274 274
275 275 self._remote.invalidate_vcs_cache()
276 276 # clear cache
277 277 self._invalidate_prop_cache('commit_ids')
278 278
279 279 return len(self.commit_ids)
280 280
281 281 def verify(self):
282 282 verify = self._remote.verify()
283 283
284 284 self._remote.invalidate_vcs_cache()
285 285 return verify
286 286
287 287 def hg_update_cache(self):
288 288 update_cache = self._remote.hg_update_cache()
289 289
290 290 self._remote.invalidate_vcs_cache()
291 291 return update_cache
292 292
293 293 def hg_rebuild_fn_cache(self):
294 294 update_cache = self._remote.hg_rebuild_fn_cache()
295 295
296 296 self._remote.invalidate_vcs_cache()
297 297 return update_cache
298 298
299 299 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
300 300 log.debug('Calculating common ancestor between %sc1:%s and %sc2:%s',
301 301 self, commit_id1, repo2, commit_id2)
302 302
303 303 if commit_id1 == commit_id2:
304 304 return commit_id1
305 305
306 306 ancestors = self._remote.revs_from_revspec(
307 307 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
308 308 other_path=repo2.path)
309 309
310 310 ancestor_id = repo2[ancestors[0]].raw_id if ancestors else None
311 311
312 312 log.debug('Found common ancestor with sha: %s', ancestor_id)
313 313 return ancestor_id
314 314
315 315 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
316 316 if commit_id1 == commit_id2:
317 317 commits = []
318 318 else:
319 319 if merge:
320 320 indexes = self._remote.revs_from_revspec(
321 321 "ancestors(id(%s)) - ancestors(id(%s)) - id(%s)",
322 322 commit_id2, commit_id1, commit_id1, other_path=repo2.path)
323 323 else:
324 324 indexes = self._remote.revs_from_revspec(
325 325 "id(%s)..id(%s) - id(%s)", commit_id1, commit_id2,
326 326 commit_id1, other_path=repo2.path)
327 327
328 328 commits = [repo2.get_commit(commit_idx=idx, pre_load=pre_load)
329 329 for idx in indexes]
330 330
331 331 return commits
332 332
333 333 @staticmethod
334 334 def check_url(url, config):
335 335 """
336 336 Function will check given url and try to verify if it's a valid
337 337 link. Sometimes it may happened that mercurial will issue basic
338 338 auth request that can cause whole API to hang when used from python
339 339 or other external calls.
340 340
341 341 On failures it'll raise urllib2.HTTPError, exception is also thrown
342 342 when the return code is non 200
343 343 """
344 344 # check first if it's not an local url
345 345 if os.path.isdir(url) or url.startswith('file:'):
346 346 return True
347 347
348 348 # Request the _remote to verify the url
349 349 return connection.Hg.check_url(url, config.serialize())
350 350
351 351 @staticmethod
352 352 def is_valid_repository(path):
353 353 return os.path.isdir(os.path.join(path, '.hg'))
354 354
355 355 def _init_repo(self, create, src_url=None, do_workspace_checkout=False):
356 356 """
357 357 Function will check for mercurial repository in given path. If there
358 358 is no repository in that path it will raise an exception unless
359 359 `create` parameter is set to True - in that case repository would
360 360 be created.
361 361
362 362 If `src_url` is given, would try to clone repository from the
363 363 location at given clone_point. Additionally it'll make update to
364 364 working copy accordingly to `do_workspace_checkout` flag.
365 365 """
366 366 if create and os.path.exists(self.path):
367 367 raise RepositoryError(
368 368 "Cannot create repository at %s, location already exist"
369 369 % self.path)
370 370
371 371 if src_url:
372 372 url = str(self._get_url(src_url))
373 373 MercurialRepository.check_url(url, self.config)
374 374
375 375 self._remote.clone(url, self.path, do_workspace_checkout)
376 376
377 377 # Don't try to create if we've already cloned repo
378 378 create = False
379 379
380 380 if create:
381 381 os.makedirs(self.path, mode=0o755)
382 382 self._remote.localrepository(create)
383 383
384 384 @LazyProperty
385 385 def in_memory_commit(self):
386 386 return MercurialInMemoryCommit(self)
387 387
388 388 @LazyProperty
389 389 def description(self):
390 390 description = self._remote.get_config_value(
391 391 'web', 'description', untrusted=True)
392 392 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
393 393
394 394 @LazyProperty
395 395 def contact(self):
396 396 contact = (
397 397 self._remote.get_config_value("web", "contact") or
398 398 self._remote.get_config_value("ui", "username"))
399 399 return safe_unicode(contact or self.DEFAULT_CONTACT)
400 400
401 401 @LazyProperty
402 402 def last_change(self):
403 403 """
404 404 Returns last change made on this repository as
405 405 `datetime.datetime` object.
406 406 """
407 407 try:
408 408 return self.get_commit().date
409 409 except RepositoryError:
410 410 tzoffset = makedate()[1]
411 411 return utcdate_fromtimestamp(self._get_fs_mtime(), tzoffset)
412 412
413 413 def _get_fs_mtime(self):
414 414 # fallback to filesystem
415 415 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
416 416 st_path = os.path.join(self.path, '.hg', "store")
417 417 if os.path.exists(cl_path):
418 418 return os.stat(cl_path).st_mtime
419 419 else:
420 420 return os.stat(st_path).st_mtime
421 421
422 422 def _get_url(self, url):
423 423 """
424 424 Returns normalized url. If schema is not given, would fall
425 425 to filesystem
426 426 (``file:///``) schema.
427 427 """
428 428 url = url.encode('utf8')
429 429 if url != 'default' and '://' not in url:
430 430 url = "file:" + urllib.pathname2url(url)
431 431 return url
432 432
433 433 def get_hook_location(self):
434 434 """
435 435 returns absolute path to location where hooks are stored
436 436 """
437 437 return os.path.join(self.path, '.hg', '.hgrc')
438 438
439 439 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None,
440 440 translate_tag=None, maybe_unreachable=False):
441 441 """
442 442 Returns ``MercurialCommit`` object representing repository's
443 443 commit at the given `commit_id` or `commit_idx`.
444 444 """
445 445 if self.is_empty():
446 446 raise EmptyRepositoryError("There are no commits yet")
447 447
448 448 if commit_id is not None:
449 449 self._validate_commit_id(commit_id)
450 450 try:
451 451 # we have cached idx, use it without contacting the remote
452 452 idx = self._commit_ids[commit_id]
453 453 return MercurialCommit(self, commit_id, idx, pre_load=pre_load)
454 454 except KeyError:
455 455 pass
456 456
457 457 elif commit_idx is not None:
458 458 self._validate_commit_idx(commit_idx)
459 459 try:
460 460 _commit_id = self.commit_ids[commit_idx]
461 461 if commit_idx < 0:
462 462 commit_idx = self.commit_ids.index(_commit_id)
463 463
464 464 return MercurialCommit(self, _commit_id, commit_idx, pre_load=pre_load)
465 465 except IndexError:
466 466 commit_id = commit_idx
467 467 else:
468 468 commit_id = "tip"
469 469
470 470 if isinstance(commit_id, unicode):
471 471 commit_id = safe_str(commit_id)
472 472
473 473 try:
474 474 raw_id, idx = self._remote.lookup(commit_id, both=True)
475 475 except CommitDoesNotExistError:
476 476 msg = "Commit {} does not exist for `{}`".format(
477 477 *map(safe_str, [commit_id, self.name]))
478 478 raise CommitDoesNotExistError(msg)
479 479
480 480 return MercurialCommit(self, raw_id, idx, pre_load=pre_load)
481 481
482 482 def get_commits(
483 483 self, start_id=None, end_id=None, start_date=None, end_date=None,
484 484 branch_name=None, show_hidden=False, pre_load=None, translate_tags=None):
485 485 """
486 486 Returns generator of ``MercurialCommit`` objects from start to end
487 487 (both are inclusive)
488 488
489 489 :param start_id: None, str(commit_id)
490 490 :param end_id: None, str(commit_id)
491 491 :param start_date: if specified, commits with commit date less than
492 492 ``start_date`` would be filtered out from returned set
493 493 :param end_date: if specified, commits with commit date greater than
494 494 ``end_date`` would be filtered out from returned set
495 495 :param branch_name: if specified, commits not reachable from given
496 496 branch would be filtered out from returned set
497 497 :param show_hidden: Show hidden commits such as obsolete or hidden from
498 498 Mercurial evolve
499 499 :raise BranchDoesNotExistError: If given ``branch_name`` does not
500 500 exist.
501 501 :raise CommitDoesNotExistError: If commit for given ``start`` or
502 502 ``end`` could not be found.
503 503 """
504 504 # actually we should check now if it's not an empty repo
505 505 if self.is_empty():
506 506 raise EmptyRepositoryError("There are no commits yet")
507 507 self._validate_branch_name(branch_name)
508 508
509 509 branch_ancestors = False
510 510 if start_id is not None:
511 511 self._validate_commit_id(start_id)
512 512 c_start = self.get_commit(commit_id=start_id)
513 513 start_pos = self._commit_ids[c_start.raw_id]
514 514 else:
515 515 start_pos = None
516 516
517 517 if end_id is not None:
518 518 self._validate_commit_id(end_id)
519 519 c_end = self.get_commit(commit_id=end_id)
520 520 end_pos = max(0, self._commit_ids[c_end.raw_id])
521 521 else:
522 522 end_pos = None
523 523
524 524 if None not in [start_id, end_id] and start_pos > end_pos:
525 525 raise RepositoryError(
526 526 "Start commit '%s' cannot be after end commit '%s'" %
527 527 (start_id, end_id))
528 528
529 529 if end_pos is not None:
530 530 end_pos += 1
531 531
532 532 commit_filter = []
533 533
534 534 if branch_name and not branch_ancestors:
535 535 commit_filter.append('branch("%s")' % (branch_name,))
536 536 elif branch_name and branch_ancestors:
537 537 commit_filter.append('ancestors(branch("%s"))' % (branch_name,))
538 538
539 539 if start_date and not end_date:
540 540 commit_filter.append('date(">%s")' % (start_date,))
541 541 if end_date and not start_date:
542 542 commit_filter.append('date("<%s")' % (end_date,))
543 543 if start_date and end_date:
544 544 commit_filter.append(
545 545 'date(">%s") and date("<%s")' % (start_date, end_date))
546 546
547 547 if not show_hidden:
548 548 commit_filter.append('not obsolete()')
549 549 commit_filter.append('not hidden()')
550 550
551 551 # TODO: johbo: Figure out a simpler way for this solution
552 552 collection_generator = CollectionGenerator
553 553 if commit_filter:
554 554 commit_filter = ' and '.join(map(safe_str, commit_filter))
555 555 revisions = self._remote.rev_range([commit_filter])
556 556 collection_generator = MercurialIndexBasedCollectionGenerator
557 557 else:
558 558 revisions = self.commit_ids
559 559
560 560 if start_pos or end_pos:
561 561 revisions = revisions[start_pos:end_pos]
562 562
563 563 return collection_generator(self, revisions, pre_load=pre_load)
564 564
565 565 def pull(self, url, commit_ids=None):
566 566 """
567 567 Pull changes from external location.
568 568
569 569 :param commit_ids: Optional. Can be set to a list of commit ids
570 570 which shall be pulled from the other repository.
571 571 """
572 572 url = self._get_url(url)
573 573 self._remote.pull(url, commit_ids=commit_ids)
574 574 self._remote.invalidate_vcs_cache()
575 575
576 576 def fetch(self, url, commit_ids=None):
577 577 """
578 578 Backward compatibility with GIT fetch==pull
579 579 """
580 580 return self.pull(url, commit_ids=commit_ids)
581 581
582 582 def push(self, url):
583 583 url = self._get_url(url)
584 584 self._remote.sync_push(url)
585 585
586 586 def _local_clone(self, clone_path):
587 587 """
588 588 Create a local clone of the current repo.
589 589 """
590 590 self._remote.clone(self.path, clone_path, update_after_clone=True,
591 591 hooks=False)
592 592
593 593 def _update(self, revision, clean=False):
594 594 """
595 595 Update the working copy to the specified revision.
596 596 """
597 597 log.debug('Doing checkout to commit: `%s` for %s', revision, self)
598 598 self._remote.update(revision, clean=clean)
599 599
600 600 def _identify(self):
601 601 """
602 602 Return the current state of the working directory.
603 603 """
604 604 return self._remote.identify().strip().rstrip('+')
605 605
606 606 def _heads(self, branch=None):
607 607 """
608 608 Return the commit ids of the repository heads.
609 609 """
610 610 return self._remote.heads(branch=branch).strip().split(' ')
611 611
612 612 def _ancestor(self, revision1, revision2):
613 613 """
614 614 Return the common ancestor of the two revisions.
615 615 """
616 616 return self._remote.ancestor(revision1, revision2)
617 617
618 618 def _local_push(
619 619 self, revision, repository_path, push_branches=False,
620 620 enable_hooks=False):
621 621 """
622 622 Push the given revision to the specified repository.
623 623
624 624 :param push_branches: allow to create branches in the target repo.
625 625 """
626 626 self._remote.push(
627 627 [revision], repository_path, hooks=enable_hooks,
628 628 push_branches=push_branches)
629 629
630 630 def _local_merge(self, target_ref, merge_message, user_name, user_email,
631 631 source_ref, use_rebase=False, close_commit_id=None, dry_run=False):
632 632 """
633 633 Merge the given source_revision into the checked out revision.
634 634
635 635 Returns the commit id of the merge and a boolean indicating if the
636 636 commit needs to be pushed.
637 637 """
638 638 source_ref_commit_id = source_ref.commit_id
639 639 target_ref_commit_id = target_ref.commit_id
640 640
641 641 # update our workdir to target ref, for proper merge
642 642 self._update(target_ref_commit_id, clean=True)
643 643
644 644 ancestor = self._ancestor(target_ref_commit_id, source_ref_commit_id)
645 645 is_the_same_branch = self._is_the_same_branch(target_ref, source_ref)
646 646
647 647 if close_commit_id:
648 648 # NOTE(marcink): if we get the close commit, this is our new source
649 649 # which will include the close commit itself.
650 650 source_ref_commit_id = close_commit_id
651 651
652 652 if ancestor == source_ref_commit_id:
653 653 # Nothing to do, the changes were already integrated
654 654 return target_ref_commit_id, False
655 655
656 656 elif ancestor == target_ref_commit_id and is_the_same_branch:
657 657 # In this case we should force a commit message
658 658 return source_ref_commit_id, True
659 659
660 660 unresolved = None
661 661 if use_rebase:
662 662 try:
663 663 bookmark_name = 'rcbook%s%s' % (source_ref_commit_id, target_ref_commit_id)
664 664 self.bookmark(bookmark_name, revision=source_ref.commit_id)
665 665 self._remote.rebase(
666 666 source=source_ref_commit_id, dest=target_ref_commit_id)
667 667 self._remote.invalidate_vcs_cache()
668 668 self._update(bookmark_name, clean=True)
669 669 return self._identify(), True
670 670 except RepositoryError as e:
671 671 # The rebase-abort may raise another exception which 'hides'
672 672 # the original one, therefore we log it here.
673 673 log.exception('Error while rebasing shadow repo during merge.')
674 674 if 'unresolved conflicts' in safe_str(e):
675 675 unresolved = self._remote.get_unresolved_files()
676 676 log.debug('unresolved files: %s', unresolved)
677 677
678 678 # Cleanup any rebase leftovers
679 679 self._remote.invalidate_vcs_cache()
680 680 self._remote.rebase(abort=True)
681 681 self._remote.invalidate_vcs_cache()
682 682 self._remote.update(clean=True)
683 683 if unresolved:
684 684 raise UnresolvedFilesInRepo(unresolved)
685 685 else:
686 686 raise
687 687 else:
688 688 try:
689 689 self._remote.merge(source_ref_commit_id)
690 690 self._remote.invalidate_vcs_cache()
691 691 self._remote.commit(
692 692 message=safe_str(merge_message),
693 693 username=safe_str('%s <%s>' % (user_name, user_email)))
694 694 self._remote.invalidate_vcs_cache()
695 695 return self._identify(), True
696 696 except RepositoryError as e:
697 697 # The merge-abort may raise another exception which 'hides'
698 698 # the original one, therefore we log it here.
699 699 log.exception('Error while merging shadow repo during merge.')
700 700 if 'unresolved merge conflicts' in safe_str(e):
701 701 unresolved = self._remote.get_unresolved_files()
702 702 log.debug('unresolved files: %s', unresolved)
703 703
704 704 # Cleanup any merge leftovers
705 705 self._remote.update(clean=True)
706 706 if unresolved:
707 707 raise UnresolvedFilesInRepo(unresolved)
708 708 else:
709 709 raise
710 710
711 711 def _local_close(self, target_ref, user_name, user_email,
712 712 source_ref, close_message=''):
713 713 """
714 714 Close the branch of the given source_revision
715 715
716 716 Returns the commit id of the close and a boolean indicating if the
717 717 commit needs to be pushed.
718 718 """
719 719 self._update(source_ref.commit_id)
720 720 message = close_message or "Closing branch: `{}`".format(source_ref.name)
721 721 try:
722 722 self._remote.commit(
723 723 message=safe_str(message),
724 724 username=safe_str('%s <%s>' % (user_name, user_email)),
725 725 close_branch=True)
726 726 self._remote.invalidate_vcs_cache()
727 727 return self._identify(), True
728 728 except RepositoryError:
729 729 # Cleanup any commit leftovers
730 730 self._remote.update(clean=True)
731 731 raise
732 732
733 733 def _is_the_same_branch(self, target_ref, source_ref):
734 734 return (
735 735 self._get_branch_name(target_ref) ==
736 736 self._get_branch_name(source_ref))
737 737
738 738 def _get_branch_name(self, ref):
739 739 if ref.type == 'branch':
740 740 return ref.name
741 741 return self._remote.ctx_branch(ref.commit_id)
742 742
743 743 def _maybe_prepare_merge_workspace(
744 744 self, repo_id, workspace_id, unused_target_ref, unused_source_ref):
745 745 shadow_repository_path = self._get_shadow_repository_path(
746 746 self.path, repo_id, workspace_id)
747 747 if not os.path.exists(shadow_repository_path):
748 748 self._local_clone(shadow_repository_path)
749 749 log.debug(
750 750 'Prepared shadow repository in %s', shadow_repository_path)
751 751
752 752 return shadow_repository_path
753 753
754 754 def _merge_repo(self, repo_id, workspace_id, target_ref,
755 755 source_repo, source_ref, merge_message,
756 756 merger_name, merger_email, dry_run=False,
757 757 use_rebase=False, close_branch=False):
758 758
759 759 log.debug('Executing merge_repo with %s strategy, dry_run mode:%s',
760 760 'rebase' if use_rebase else 'merge', dry_run)
761 761 if target_ref.commit_id not in self._heads():
762 762 return MergeResponse(
763 763 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD,
764 764 metadata={'target_ref': target_ref})
765 765
766 766 try:
767 767 if target_ref.type == 'branch' and len(self._heads(target_ref.name)) != 1:
768 heads = '\n,'.join(self._heads(target_ref.name))
768 heads_all = self._heads(target_ref.name)
769 max_heads = 10
770 if len(heads_all) > max_heads:
771 heads = '\n,'.join(
772 heads_all[:max_heads] +
773 ['and {} more.'.format(len(heads_all)-max_heads)])
774 else:
775 heads = '\n,'.join(heads_all)
769 776 metadata = {
770 777 'target_ref': target_ref,
771 778 'source_ref': source_ref,
772 779 'heads': heads
773 780 }
774 781 return MergeResponse(
775 782 False, False, None,
776 783 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
777 784 metadata=metadata)
778 785 except CommitDoesNotExistError:
779 786 log.exception('Failure when looking up branch heads on hg target')
780 787 return MergeResponse(
781 788 False, False, None, MergeFailureReason.MISSING_TARGET_REF,
782 789 metadata={'target_ref': target_ref})
783 790
784 791 shadow_repository_path = self._maybe_prepare_merge_workspace(
785 792 repo_id, workspace_id, target_ref, source_ref)
786 793 shadow_repo = self.get_shadow_instance(shadow_repository_path)
787 794
788 795 log.debug('Pulling in target reference %s', target_ref)
789 796 self._validate_pull_reference(target_ref)
790 797 shadow_repo._local_pull(self.path, target_ref)
791 798
792 799 try:
793 800 log.debug('Pulling in source reference %s', source_ref)
794 801 source_repo._validate_pull_reference(source_ref)
795 802 shadow_repo._local_pull(source_repo.path, source_ref)
796 803 except CommitDoesNotExistError:
797 804 log.exception('Failure when doing local pull on hg shadow repo')
798 805 return MergeResponse(
799 806 False, False, None, MergeFailureReason.MISSING_SOURCE_REF,
800 807 metadata={'source_ref': source_ref})
801 808
802 809 merge_ref = None
803 810 merge_commit_id = None
804 811 close_commit_id = None
805 812 merge_failure_reason = MergeFailureReason.NONE
806 813 metadata = {}
807 814
808 815 # enforce that close branch should be used only in case we source from
809 816 # an actual Branch
810 817 close_branch = close_branch and source_ref.type == 'branch'
811 818
812 819 # don't allow to close branch if source and target are the same
813 820 close_branch = close_branch and source_ref.name != target_ref.name
814 821
815 822 needs_push_on_close = False
816 823 if close_branch and not use_rebase and not dry_run:
817 824 try:
818 825 close_commit_id, needs_push_on_close = shadow_repo._local_close(
819 826 target_ref, merger_name, merger_email, source_ref)
820 827 merge_possible = True
821 828 except RepositoryError:
822 829 log.exception('Failure when doing close branch on '
823 830 'shadow repo: %s', shadow_repo)
824 831 merge_possible = False
825 832 merge_failure_reason = MergeFailureReason.MERGE_FAILED
826 833 else:
827 834 merge_possible = True
828 835
829 836 needs_push = False
830 837 if merge_possible:
831 838
832 839 try:
833 840 merge_commit_id, needs_push = shadow_repo._local_merge(
834 841 target_ref, merge_message, merger_name, merger_email,
835 842 source_ref, use_rebase=use_rebase,
836 843 close_commit_id=close_commit_id, dry_run=dry_run)
837 844 merge_possible = True
838 845
839 846 # read the state of the close action, if it
840 847 # maybe required a push
841 848 needs_push = needs_push or needs_push_on_close
842 849
843 850 # Set a bookmark pointing to the merge commit. This bookmark
844 851 # may be used to easily identify the last successful merge
845 852 # commit in the shadow repository.
846 853 shadow_repo.bookmark('pr-merge', revision=merge_commit_id)
847 854 merge_ref = Reference('book', 'pr-merge', merge_commit_id)
848 855 except SubrepoMergeError:
849 856 log.exception(
850 857 'Subrepo merge error during local merge on hg shadow repo.')
851 858 merge_possible = False
852 859 merge_failure_reason = MergeFailureReason.SUBREPO_MERGE_FAILED
853 860 needs_push = False
854 861 except RepositoryError as e:
855 862 log.exception('Failure when doing local merge on hg shadow repo')
856 863 if isinstance(e, UnresolvedFilesInRepo):
857 metadata['unresolved_files'] = '\n* conflict: ' + ('\n * conflict: '.join(e.args[0]))
864 all_conflicts = list(e.args[0])
865 max_conflicts = 20
866 if len(all_conflicts) > max_conflicts:
867 conflicts = all_conflicts[:max_conflicts] \
868 + ['and {} more.'.format(len(all_conflicts)-max_conflicts)]
869 else:
870 conflicts = all_conflicts
871 metadata['unresolved_files'] = \
872 '\n* conflict: ' + \
873 ('\n * conflict: '.join(conflicts))
858 874
859 875 merge_possible = False
860 876 merge_failure_reason = MergeFailureReason.MERGE_FAILED
861 877 needs_push = False
862 878
863 879 if merge_possible and not dry_run:
864 880 if needs_push:
865 881 # In case the target is a bookmark, update it, so after pushing
866 882 # the bookmarks is also updated in the target.
867 883 if target_ref.type == 'book':
868 884 shadow_repo.bookmark(
869 885 target_ref.name, revision=merge_commit_id)
870 886 try:
871 887 shadow_repo_with_hooks = self.get_shadow_instance(
872 888 shadow_repository_path,
873 889 enable_hooks=True)
874 890 # This is the actual merge action, we push from shadow
875 891 # into origin.
876 892 # Note: the push_branches option will push any new branch
877 893 # defined in the source repository to the target. This may
878 894 # be dangerous as branches are permanent in Mercurial.
879 895 # This feature was requested in issue #441.
880 896 shadow_repo_with_hooks._local_push(
881 897 merge_commit_id, self.path, push_branches=True,
882 898 enable_hooks=True)
883 899
884 900 # maybe we also need to push the close_commit_id
885 901 if close_commit_id:
886 902 shadow_repo_with_hooks._local_push(
887 903 close_commit_id, self.path, push_branches=True,
888 904 enable_hooks=True)
889 905 merge_succeeded = True
890 906 except RepositoryError:
891 907 log.exception(
892 908 'Failure when doing local push from the shadow '
893 909 'repository to the target repository at %s.', self.path)
894 910 merge_succeeded = False
895 911 merge_failure_reason = MergeFailureReason.PUSH_FAILED
896 912 metadata['target'] = 'hg shadow repo'
897 913 metadata['merge_commit'] = merge_commit_id
898 914 else:
899 915 merge_succeeded = True
900 916 else:
901 917 merge_succeeded = False
902 918
903 919 return MergeResponse(
904 920 merge_possible, merge_succeeded, merge_ref, merge_failure_reason,
905 921 metadata=metadata)
906 922
907 923 def get_shadow_instance(self, shadow_repository_path, enable_hooks=False, cache=False):
908 924 config = self.config.copy()
909 925 if not enable_hooks:
910 926 config.clear_section('hooks')
911 927 return MercurialRepository(shadow_repository_path, config, with_wire={"cache": cache})
912 928
913 929 def _validate_pull_reference(self, reference):
914 930 if not (reference.name in self.bookmarks or
915 931 reference.name in self.branches or
916 932 self.get_commit(reference.commit_id)):
917 933 raise CommitDoesNotExistError(
918 934 'Unknown branch, bookmark or commit id')
919 935
920 936 def _local_pull(self, repository_path, reference):
921 937 """
922 938 Fetch a branch, bookmark or commit from a local repository.
923 939 """
924 940 repository_path = os.path.abspath(repository_path)
925 941 if repository_path == self.path:
926 942 raise ValueError('Cannot pull from the same repository')
927 943
928 944 reference_type_to_option_name = {
929 945 'book': 'bookmark',
930 946 'branch': 'branch',
931 947 }
932 948 option_name = reference_type_to_option_name.get(
933 949 reference.type, 'revision')
934 950
935 951 if option_name == 'revision':
936 952 ref = reference.commit_id
937 953 else:
938 954 ref = reference.name
939 955
940 956 options = {option_name: [ref]}
941 957 self._remote.pull_cmd(repository_path, hooks=False, **options)
942 958 self._remote.invalidate_vcs_cache()
943 959
944 960 def bookmark(self, bookmark, revision=None):
945 961 if isinstance(bookmark, unicode):
946 962 bookmark = safe_str(bookmark)
947 963 self._remote.bookmark(bookmark, revision=revision)
948 964 self._remote.invalidate_vcs_cache()
949 965
950 966 def get_path_permissions(self, username):
951 967 hgacl_file = os.path.join(self.path, '.hg/hgacl')
952 968
953 969 def read_patterns(suffix):
954 970 svalue = None
955 971 for section, option in [
956 972 ('narrowacl', username + suffix),
957 973 ('narrowacl', 'default' + suffix),
958 974 ('narrowhgacl', username + suffix),
959 975 ('narrowhgacl', 'default' + suffix)
960 976 ]:
961 977 try:
962 978 svalue = hgacl.get(section, option)
963 979 break # stop at the first value we find
964 980 except configparser.NoOptionError:
965 981 pass
966 982 if not svalue:
967 983 return None
968 984 result = ['/']
969 985 for pattern in svalue.split():
970 986 result.append(pattern)
971 987 if '*' not in pattern and '?' not in pattern:
972 988 result.append(pattern + '/*')
973 989 return result
974 990
975 991 if os.path.exists(hgacl_file):
976 992 try:
977 993 hgacl = configparser.RawConfigParser()
978 994 hgacl.read(hgacl_file)
979 995
980 996 includes = read_patterns('.includes')
981 997 excludes = read_patterns('.excludes')
982 998 return BasePathPermissionChecker.create_from_patterns(
983 999 includes, excludes)
984 1000 except BaseException as e:
985 1001 msg = 'Cannot read ACL settings from {} on {}: {}'.format(
986 1002 hgacl_file, self.name, e)
987 1003 raise exceptions.RepositoryRequirementError(msg)
988 1004 else:
989 1005 return None
990 1006
991 1007
992 1008 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
993 1009
994 1010 def _commit_factory(self, commit_id):
995 1011 return self.repo.get_commit(
996 1012 commit_idx=commit_id, pre_load=self.pre_load)
@@ -1,234 +1,234 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Custom vcs exceptions module.
23 23 """
24 24 import logging
25 25 import functools
26 26 import urllib2
27 27 import rhodecode
28 28 from pyramid import compat
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 class VCSCommunicationError(Exception):
34 34 pass
35 35
36 36
37 37 class HttpVCSCommunicationError(VCSCommunicationError):
38 38 pass
39 39
40 40
41 41 class VCSError(Exception):
42 42 pass
43 43
44 44
45 45 class RepositoryError(VCSError):
46 46 pass
47 47
48 48
49 49 class RepositoryRequirementError(RepositoryError):
50 50 pass
51 51
52 52
53 53 class UnresolvedFilesInRepo(RepositoryError):
54 54 pass
55 55
56 56
57 57 class VCSBackendNotSupportedError(VCSError):
58 58 """
59 59 Exception raised when VCSServer does not support requested backend
60 60 """
61 61
62 62
63 63 class EmptyRepositoryError(RepositoryError):
64 64 pass
65 65
66 66
67 67 class TagAlreadyExistError(RepositoryError):
68 68 pass
69 69
70 70
71 71 class TagDoesNotExistError(RepositoryError):
72 72 pass
73 73
74 74
75 75 class BranchAlreadyExistError(RepositoryError):
76 76 pass
77 77
78 78
79 79 class BranchDoesNotExistError(RepositoryError):
80 80 pass
81 81
82 82
83 83 class CommitError(RepositoryError):
84 84 """
85 85 Exceptions related to an existing commit
86 86 """
87 87
88 88
89 89 class CommitDoesNotExistError(CommitError):
90 90 pass
91 91
92 92
93 93 class CommittingError(RepositoryError):
94 94 """
95 95 Exceptions happening while creating a new commit
96 96 """
97 97
98 98
99 99 class NothingChangedError(CommittingError):
100 100 pass
101 101
102 102
103 103 class NodeError(VCSError):
104 104 pass
105 105
106 106
107 107 class RemovedFileNodeError(NodeError):
108 108 pass
109 109
110 110
111 111 class NodeAlreadyExistsError(CommittingError):
112 112 pass
113 113
114 114
115 115 class NodeAlreadyChangedError(CommittingError):
116 116 pass
117 117
118 118
119 119 class NodeDoesNotExistError(CommittingError):
120 120 pass
121 121
122 122
123 123 class NodeNotChangedError(CommittingError):
124 124 pass
125 125
126 126
127 127 class NodeAlreadyAddedError(CommittingError):
128 128 pass
129 129
130 130
131 131 class NodeAlreadyRemovedError(CommittingError):
132 132 pass
133 133
134 134
135 135 class SubrepoMergeError(RepositoryError):
136 136 """
137 137 This happens if we try to merge a repository which contains subrepos and
138 138 the subrepos cannot be merged. The subrepos are not merged itself but
139 139 their references in the root repo are merged.
140 140 """
141 141
142 142
143 143 class ImproperArchiveTypeError(VCSError):
144 144 pass
145 145
146 146
147 147 class CommandError(VCSError):
148 148 pass
149 149
150 150
151 151 class UnhandledException(VCSError):
152 152 """
153 153 Signals that something unexpected went wrong.
154 154
155 155 This usually means we have a programming error on the side of the VCSServer
156 156 and should inspect the logfile of the VCSServer to find more details.
157 157 """
158 158
159 159
160 160 _EXCEPTION_MAP = {
161 161 'abort': RepositoryError,
162 162 'archive': ImproperArchiveTypeError,
163 163 'error': RepositoryError,
164 164 'lookup': CommitDoesNotExistError,
165 165 'repo_locked': RepositoryError,
166 166 'requirement': RepositoryRequirementError,
167 167 'unhandled': UnhandledException,
168 168 # TODO: johbo: Define our own exception for this and stop abusing
169 169 # urllib's exception class.
170 170 'url_error': urllib2.URLError,
171 171 'subrepo_merge_error': SubrepoMergeError,
172 172 }
173 173
174 174
175 175 def map_vcs_exceptions(func):
176 176 """
177 177 Utility to decorate functions so that plain exceptions are translated.
178 178
179 179 The translation is based on `exc_map` which maps a `str` indicating
180 180 the error type into an exception class representing this error inside
181 181 of the vcs layer.
182 182 """
183 183
184 184 @functools.wraps(func)
185 185 def wrapper(*args, **kwargs):
186 186 try:
187 187 return func(*args, **kwargs)
188 188 except Exception as e:
189 189 from rhodecode.lib.utils2 import str2bool
190 190 debug = str2bool(rhodecode.CONFIG.get('debug'))
191 191
192 192 # The error middleware adds information if it finds
193 193 # __traceback_info__ in a frame object. This way the remote
194 194 # traceback information is made available in error reports.
195 195 remote_tb = getattr(e, '_vcs_server_traceback', None)
196 196 org_remote_tb = getattr(e, '_vcs_server_org_exc_tb', '')
197 197 __traceback_info__ = None
198 198 if remote_tb:
199 199 if isinstance(remote_tb, compat.string_types):
200 200 remote_tb = [remote_tb]
201 201 __traceback_info__ = (
202 202 'Found VCSServer remote traceback information:\n'
203 203 '{}\n'
204 204 '+++ BEG SOURCE EXCEPTION +++\n\n'
205 205 '{}\n'
206 206 '+++ END SOURCE EXCEPTION +++\n'
207 207 ''.format('\n'.join(remote_tb), org_remote_tb)
208 208 )
209 209
210 210 # Avoid that remote_tb also appears in the frame
211 211 del remote_tb
212 212
213 213 # Special vcs errors had an attribute "_vcs_kind" which is used
214 214 # to translate them to the proper exception class in the vcs
215 215 # client layer.
216 216 kind = getattr(e, '_vcs_kind', None)
217 217 exc_name = getattr(e, '_vcs_server_org_exc_name', None)
218 218
219 219 if kind:
220 220 if any(e.args):
221 221 _args = [a for a in e.args]
222 222 # replace the first argument with a prefix exc name
223 args = ['{}:'.format(exc_name, _args[0] if _args else '?')] + _args[1:]
223 args = ['{}:{}'.format(exc_name, _args[0] if _args else '?')] + _args[1:]
224 224 else:
225 225 args = [__traceback_info__ or '{}: UnhandledException'.format(exc_name)]
226 226 if debug or __traceback_info__ and kind not in ['unhandled', 'lookup']:
227 227 # for other than unhandled errors also log the traceback
228 228 # can be useful for debugging
229 229 log.error(__traceback_info__)
230 230
231 231 raise _EXCEPTION_MAP[kind](*args)
232 232 else:
233 233 raise
234 234 return wrapper
@@ -1,852 +1,857 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 comments model for RhodeCode
23 23 """
24 24 import datetime
25 25
26 26 import logging
27 27 import traceback
28 28 import collections
29 29
30 30 from pyramid.threadlocal import get_current_registry, get_current_request
31 31 from sqlalchemy.sql.expression import null
32 32 from sqlalchemy.sql.functions import coalesce
33 33
34 34 from rhodecode.lib import helpers as h, diffs, channelstream, hooks_utils
35 35 from rhodecode.lib import audit_logger
36 36 from rhodecode.lib.exceptions import CommentVersionMismatch
37 37 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import (
40 40 false, true,
41 41 ChangesetComment,
42 42 User,
43 43 Notification,
44 44 PullRequest,
45 45 AttributeDict,
46 46 ChangesetCommentHistory,
47 47 )
48 48 from rhodecode.model.notification import NotificationModel
49 49 from rhodecode.model.meta import Session
50 50 from rhodecode.model.settings import VcsSettingsModel
51 51 from rhodecode.model.notification import EmailNotificationModel
52 52 from rhodecode.model.validation_schema.schemas import comment_schema
53 53
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class CommentsModel(BaseModel):
59 59
60 60 cls = ChangesetComment
61 61
62 62 DIFF_CONTEXT_BEFORE = 3
63 63 DIFF_CONTEXT_AFTER = 3
64 64
65 65 def __get_commit_comment(self, changeset_comment):
66 66 return self._get_instance(ChangesetComment, changeset_comment)
67 67
68 68 def __get_pull_request(self, pull_request):
69 69 return self._get_instance(PullRequest, pull_request)
70 70
71 71 def _extract_mentions(self, s):
72 72 user_objects = []
73 73 for username in extract_mentioned_users(s):
74 74 user_obj = User.get_by_username(username, case_insensitive=True)
75 75 if user_obj:
76 76 user_objects.append(user_obj)
77 77 return user_objects
78 78
79 79 def _get_renderer(self, global_renderer='rst', request=None):
80 80 request = request or get_current_request()
81 81
82 82 try:
83 83 global_renderer = request.call_context.visual.default_renderer
84 84 except AttributeError:
85 85 log.debug("Renderer not set, falling back "
86 86 "to default renderer '%s'", global_renderer)
87 87 except Exception:
88 88 log.error(traceback.format_exc())
89 89 return global_renderer
90 90
91 91 def aggregate_comments(self, comments, versions, show_version, inline=False):
92 92 # group by versions, and count until, and display objects
93 93
94 94 comment_groups = collections.defaultdict(list)
95 95 [comment_groups[_co.pull_request_version_id].append(_co) for _co in comments]
96 96
97 97 def yield_comments(pos):
98 98 for co in comment_groups[pos]:
99 99 yield co
100 100
101 101 comment_versions = collections.defaultdict(
102 102 lambda: collections.defaultdict(list))
103 103 prev_prvid = -1
104 104 # fake last entry with None, to aggregate on "latest" version which
105 105 # doesn't have an pull_request_version_id
106 106 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
107 107 prvid = ver.pull_request_version_id
108 108 if prev_prvid == -1:
109 109 prev_prvid = prvid
110 110
111 111 for co in yield_comments(prvid):
112 112 comment_versions[prvid]['at'].append(co)
113 113
114 114 # save until
115 115 current = comment_versions[prvid]['at']
116 116 prev_until = comment_versions[prev_prvid]['until']
117 117 cur_until = prev_until + current
118 118 comment_versions[prvid]['until'].extend(cur_until)
119 119
120 120 # save outdated
121 121 if inline:
122 122 outdated = [x for x in cur_until
123 123 if x.outdated_at_version(show_version)]
124 124 else:
125 125 outdated = [x for x in cur_until
126 126 if x.older_than_version(show_version)]
127 127 display = [x for x in cur_until if x not in outdated]
128 128
129 129 comment_versions[prvid]['outdated'] = outdated
130 130 comment_versions[prvid]['display'] = display
131 131
132 132 prev_prvid = prvid
133 133
134 134 return comment_versions
135 135
136 136 def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None):
137 137 qry = Session().query(ChangesetComment) \
138 138 .filter(ChangesetComment.repo == repo)
139 139
140 140 if comment_type and comment_type in ChangesetComment.COMMENT_TYPES:
141 141 qry = qry.filter(ChangesetComment.comment_type == comment_type)
142 142
143 143 if user:
144 144 user = self._get_user(user)
145 145 if user:
146 146 qry = qry.filter(ChangesetComment.user_id == user.user_id)
147 147
148 148 if commit_id:
149 149 qry = qry.filter(ChangesetComment.revision == commit_id)
150 150
151 151 qry = qry.order_by(ChangesetComment.created_on)
152 152 return qry.all()
153 153
154 154 def get_repository_unresolved_todos(self, repo):
155 155 todos = Session().query(ChangesetComment) \
156 156 .filter(ChangesetComment.repo == repo) \
157 157 .filter(ChangesetComment.resolved_by == None) \
158 158 .filter(ChangesetComment.comment_type
159 159 == ChangesetComment.COMMENT_TYPE_TODO)
160 160 todos = todos.all()
161 161
162 162 return todos
163 163
164 164 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
165 165
166 166 todos = Session().query(ChangesetComment) \
167 167 .filter(ChangesetComment.pull_request == pull_request) \
168 168 .filter(ChangesetComment.resolved_by == None) \
169 169 .filter(ChangesetComment.comment_type
170 170 == ChangesetComment.COMMENT_TYPE_TODO)
171 171
172 172 if not include_drafts:
173 173 todos = todos.filter(ChangesetComment.draft == false())
174 174
175 175 if not show_outdated:
176 176 todos = todos.filter(
177 177 coalesce(ChangesetComment.display_state, '') !=
178 178 ChangesetComment.COMMENT_OUTDATED)
179 179
180 180 todos = todos.all()
181 181
182 182 return todos
183 183
184 184 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True, include_drafts=True):
185 185
186 186 todos = Session().query(ChangesetComment) \
187 187 .filter(ChangesetComment.pull_request == pull_request) \
188 188 .filter(ChangesetComment.resolved_by != None) \
189 189 .filter(ChangesetComment.comment_type
190 190 == ChangesetComment.COMMENT_TYPE_TODO)
191 191
192 192 if not include_drafts:
193 193 todos = todos.filter(ChangesetComment.draft == false())
194 194
195 195 if not show_outdated:
196 196 todos = todos.filter(
197 197 coalesce(ChangesetComment.display_state, '') !=
198 198 ChangesetComment.COMMENT_OUTDATED)
199 199
200 200 todos = todos.all()
201 201
202 202 return todos
203 203
204 204 def get_pull_request_drafts(self, user_id, pull_request):
205 205 drafts = Session().query(ChangesetComment) \
206 206 .filter(ChangesetComment.pull_request == pull_request) \
207 207 .filter(ChangesetComment.user_id == user_id) \
208 208 .filter(ChangesetComment.draft == true())
209 209 return drafts.all()
210 210
211 211 def get_commit_unresolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
212 212
213 213 todos = Session().query(ChangesetComment) \
214 214 .filter(ChangesetComment.revision == commit_id) \
215 215 .filter(ChangesetComment.resolved_by == None) \
216 216 .filter(ChangesetComment.comment_type
217 217 == ChangesetComment.COMMENT_TYPE_TODO)
218 218
219 219 if not include_drafts:
220 220 todos = todos.filter(ChangesetComment.draft == false())
221 221
222 222 if not show_outdated:
223 223 todos = todos.filter(
224 224 coalesce(ChangesetComment.display_state, '') !=
225 225 ChangesetComment.COMMENT_OUTDATED)
226 226
227 227 todos = todos.all()
228 228
229 229 return todos
230 230
231 231 def get_commit_resolved_todos(self, commit_id, show_outdated=True, include_drafts=True):
232 232
233 233 todos = Session().query(ChangesetComment) \
234 234 .filter(ChangesetComment.revision == commit_id) \
235 235 .filter(ChangesetComment.resolved_by != None) \
236 236 .filter(ChangesetComment.comment_type
237 237 == ChangesetComment.COMMENT_TYPE_TODO)
238 238
239 239 if not include_drafts:
240 240 todos = todos.filter(ChangesetComment.draft == false())
241 241
242 242 if not show_outdated:
243 243 todos = todos.filter(
244 244 coalesce(ChangesetComment.display_state, '') !=
245 245 ChangesetComment.COMMENT_OUTDATED)
246 246
247 247 todos = todos.all()
248 248
249 249 return todos
250 250
251 251 def get_commit_inline_comments(self, commit_id, include_drafts=True):
252 252 inline_comments = Session().query(ChangesetComment) \
253 253 .filter(ChangesetComment.line_no != None) \
254 254 .filter(ChangesetComment.f_path != None) \
255 255 .filter(ChangesetComment.revision == commit_id)
256 256
257 257 if not include_drafts:
258 258 inline_comments = inline_comments.filter(ChangesetComment.draft == false())
259 259
260 260 inline_comments = inline_comments.all()
261 261 return inline_comments
262 262
263 263 def _log_audit_action(self, action, action_data, auth_user, comment):
264 264 audit_logger.store(
265 265 action=action,
266 266 action_data=action_data,
267 267 user=auth_user,
268 268 repo=comment.repo)
269 269
270 270 def create(self, text, repo, user, commit_id=None, pull_request=None,
271 271 f_path=None, line_no=None, status_change=None,
272 272 status_change_type=None, comment_type=None, is_draft=False,
273 273 resolves_comment_id=None, closing_pr=False, send_email=True,
274 274 renderer=None, auth_user=None, extra_recipients=None):
275 275 """
276 276 Creates new comment for commit or pull request.
277 277 IF status_change is not none this comment is associated with a
278 278 status change of commit or commit associated with pull request
279 279
280 280 :param text:
281 281 :param repo:
282 282 :param user:
283 283 :param commit_id:
284 284 :param pull_request:
285 285 :param f_path:
286 286 :param line_no:
287 287 :param status_change: Label for status change
288 288 :param comment_type: Type of comment
289 289 :param is_draft: is comment a draft only
290 290 :param resolves_comment_id: id of comment which this one will resolve
291 291 :param status_change_type: type of status change
292 292 :param closing_pr:
293 293 :param send_email:
294 294 :param renderer: pick renderer for this comment
295 295 :param auth_user: current authenticated user calling this method
296 296 :param extra_recipients: list of extra users to be added to recipients
297 297 """
298 298
299 299 if not text:
300 300 log.warning('Missing text for comment, skipping...')
301 301 return
302 302 request = get_current_request()
303 303 _ = request.translate
304 304
305 305 if not renderer:
306 306 renderer = self._get_renderer(request=request)
307 307
308 308 repo = self._get_repo(repo)
309 309 user = self._get_user(user)
310 310 auth_user = auth_user or user
311 311
312 312 schema = comment_schema.CommentSchema()
313 313 validated_kwargs = schema.deserialize(dict(
314 314 comment_body=text,
315 315 comment_type=comment_type,
316 316 is_draft=is_draft,
317 317 comment_file=f_path,
318 318 comment_line=line_no,
319 319 renderer_type=renderer,
320 320 status_change=status_change_type,
321 321 resolves_comment_id=resolves_comment_id,
322 322 repo=repo.repo_id,
323 323 user=user.user_id,
324 324 ))
325 325 is_draft = validated_kwargs['is_draft']
326 326
327 327 comment = ChangesetComment()
328 328 comment.renderer = validated_kwargs['renderer_type']
329 329 comment.text = validated_kwargs['comment_body']
330 330 comment.f_path = validated_kwargs['comment_file']
331 331 comment.line_no = validated_kwargs['comment_line']
332 332 comment.comment_type = validated_kwargs['comment_type']
333 333 comment.draft = is_draft
334 334
335 335 comment.repo = repo
336 336 comment.author = user
337 337 resolved_comment = self.__get_commit_comment(
338 338 validated_kwargs['resolves_comment_id'])
339
339 340 # check if the comment actually belongs to this PR
340 341 if resolved_comment and resolved_comment.pull_request and \
341 342 resolved_comment.pull_request != pull_request:
342 343 log.warning('Comment tried to resolved unrelated todo comment: %s',
343 344 resolved_comment)
344 345 # comment not bound to this pull request, forbid
345 346 resolved_comment = None
346 347
347 348 elif resolved_comment and resolved_comment.repo and \
348 349 resolved_comment.repo != repo:
349 350 log.warning('Comment tried to resolved unrelated todo comment: %s',
350 351 resolved_comment)
351 352 # comment not bound to this repo, forbid
352 353 resolved_comment = None
353 354
355 if resolved_comment and resolved_comment.resolved_by:
356 # if this comment is already resolved, don't mark it again!
357 resolved_comment = None
358
354 359 comment.resolved_comment = resolved_comment
355 360
356 361 pull_request_id = pull_request
357 362
358 363 commit_obj = None
359 364 pull_request_obj = None
360 365
361 366 if commit_id:
362 367 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
363 368 # do a lookup, so we don't pass something bad here
364 369 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
365 370 comment.revision = commit_obj.raw_id
366 371
367 372 elif pull_request_id:
368 373 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
369 374 pull_request_obj = self.__get_pull_request(pull_request_id)
370 375 comment.pull_request = pull_request_obj
371 376 else:
372 377 raise Exception('Please specify commit or pull_request_id')
373 378
374 379 Session().add(comment)
375 380 Session().flush()
376 381 kwargs = {
377 382 'user': user,
378 383 'renderer_type': renderer,
379 384 'repo_name': repo.repo_name,
380 385 'status_change': status_change,
381 386 'status_change_type': status_change_type,
382 387 'comment_body': text,
383 388 'comment_file': f_path,
384 389 'comment_line': line_no,
385 390 'comment_type': comment_type or 'note',
386 391 'comment_id': comment.comment_id
387 392 }
388 393
389 394 if commit_obj:
390 395 recipients = ChangesetComment.get_users(
391 396 revision=commit_obj.raw_id)
392 397 # add commit author if it's in RhodeCode system
393 398 cs_author = User.get_from_cs_author(commit_obj.author)
394 399 if not cs_author:
395 400 # use repo owner if we cannot extract the author correctly
396 401 cs_author = repo.user
397 402 recipients += [cs_author]
398 403
399 404 commit_comment_url = self.get_url(comment, request=request)
400 405 commit_comment_reply_url = self.get_url(
401 406 comment, request=request,
402 407 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
403 408
404 409 target_repo_url = h.link_to(
405 410 repo.repo_name,
406 411 h.route_url('repo_summary', repo_name=repo.repo_name))
407 412
408 413 commit_url = h.route_url('repo_commit', repo_name=repo.repo_name,
409 414 commit_id=commit_id)
410 415
411 416 # commit specifics
412 417 kwargs.update({
413 418 'commit': commit_obj,
414 419 'commit_message': commit_obj.message,
415 420 'commit_target_repo_url': target_repo_url,
416 421 'commit_comment_url': commit_comment_url,
417 422 'commit_comment_reply_url': commit_comment_reply_url,
418 423 'commit_url': commit_url,
419 424 'thread_ids': [commit_url, commit_comment_url],
420 425 })
421 426
422 427 elif pull_request_obj:
423 428 # get the current participants of this pull request
424 429 recipients = ChangesetComment.get_users(
425 430 pull_request_id=pull_request_obj.pull_request_id)
426 431 # add pull request author
427 432 recipients += [pull_request_obj.author]
428 433
429 434 # add the reviewers to notification
430 435 recipients += [x.user for x in pull_request_obj.get_pull_request_reviewers()]
431 436
432 437 pr_target_repo = pull_request_obj.target_repo
433 438 pr_source_repo = pull_request_obj.source_repo
434 439
435 440 pr_comment_url = self.get_url(comment, request=request)
436 441 pr_comment_reply_url = self.get_url(
437 442 comment, request=request,
438 443 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
439 444
440 445 pr_url = h.route_url(
441 446 'pullrequest_show',
442 447 repo_name=pr_target_repo.repo_name,
443 448 pull_request_id=pull_request_obj.pull_request_id, )
444 449
445 450 # set some variables for email notification
446 451 pr_target_repo_url = h.route_url(
447 452 'repo_summary', repo_name=pr_target_repo.repo_name)
448 453
449 454 pr_source_repo_url = h.route_url(
450 455 'repo_summary', repo_name=pr_source_repo.repo_name)
451 456
452 457 # pull request specifics
453 458 kwargs.update({
454 459 'pull_request': pull_request_obj,
455 460 'pr_id': pull_request_obj.pull_request_id,
456 461 'pull_request_url': pr_url,
457 462 'pull_request_target_repo': pr_target_repo,
458 463 'pull_request_target_repo_url': pr_target_repo_url,
459 464 'pull_request_source_repo': pr_source_repo,
460 465 'pull_request_source_repo_url': pr_source_repo_url,
461 466 'pr_comment_url': pr_comment_url,
462 467 'pr_comment_reply_url': pr_comment_reply_url,
463 468 'pr_closing': closing_pr,
464 469 'thread_ids': [pr_url, pr_comment_url],
465 470 })
466 471
467 472 if send_email:
468 473 recipients += [self._get_user(u) for u in (extra_recipients or [])]
469 474
470 475 mention_recipients = set(
471 476 self._extract_mentions(text)).difference(recipients)
472 477
473 478 # create notification objects, and emails
474 479 NotificationModel().create(
475 480 created_by=user,
476 481 notification_subject='', # Filled in based on the notification_type
477 482 notification_body='', # Filled in based on the notification_type
478 483 notification_type=notification_type,
479 484 recipients=recipients,
480 485 mention_recipients=mention_recipients,
481 486 email_kwargs=kwargs,
482 487 )
483 488
484 489 Session().flush()
485 490 if comment.pull_request:
486 491 action = 'repo.pull_request.comment.create'
487 492 else:
488 493 action = 'repo.commit.comment.create'
489 494
490 495 if not is_draft:
491 496 comment_data = comment.get_api_data()
492 497
493 498 self._log_audit_action(
494 499 action, {'data': comment_data}, auth_user, comment)
495 500
496 501 return comment
497 502
498 503 def edit(self, comment_id, text, auth_user, version):
499 504 """
500 505 Change existing comment for commit or pull request.
501 506
502 507 :param comment_id:
503 508 :param text:
504 509 :param auth_user: current authenticated user calling this method
505 510 :param version: last comment version
506 511 """
507 512 if not text:
508 513 log.warning('Missing text for comment, skipping...')
509 514 return
510 515
511 516 comment = ChangesetComment.get(comment_id)
512 517 old_comment_text = comment.text
513 518 comment.text = text
514 519 comment.modified_at = datetime.datetime.now()
515 520 version = safe_int(version)
516 521
517 522 # NOTE(marcink): this returns initial comment + edits, so v2 from ui
518 523 # would return 3 here
519 524 comment_version = ChangesetCommentHistory.get_version(comment_id)
520 525
521 526 if isinstance(version, (int, long)) and (comment_version - version) != 1:
522 527 log.warning(
523 528 'Version mismatch comment_version {} submitted {}, skipping'.format(
524 529 comment_version-1, # -1 since note above
525 530 version
526 531 )
527 532 )
528 533 raise CommentVersionMismatch()
529 534
530 535 comment_history = ChangesetCommentHistory()
531 536 comment_history.comment_id = comment_id
532 537 comment_history.version = comment_version
533 538 comment_history.created_by_user_id = auth_user.user_id
534 539 comment_history.text = old_comment_text
535 540 # TODO add email notification
536 541 Session().add(comment_history)
537 542 Session().add(comment)
538 543 Session().flush()
539 544
540 545 if comment.pull_request:
541 546 action = 'repo.pull_request.comment.edit'
542 547 else:
543 548 action = 'repo.commit.comment.edit'
544 549
545 550 comment_data = comment.get_api_data()
546 551 comment_data['old_comment_text'] = old_comment_text
547 552 self._log_audit_action(
548 553 action, {'data': comment_data}, auth_user, comment)
549 554
550 555 return comment_history
551 556
552 557 def delete(self, comment, auth_user):
553 558 """
554 559 Deletes given comment
555 560 """
556 561 comment = self.__get_commit_comment(comment)
557 562 old_data = comment.get_api_data()
558 563 Session().delete(comment)
559 564
560 565 if comment.pull_request:
561 566 action = 'repo.pull_request.comment.delete'
562 567 else:
563 568 action = 'repo.commit.comment.delete'
564 569
565 570 self._log_audit_action(
566 571 action, {'old_data': old_data}, auth_user, comment)
567 572
568 573 return comment
569 574
570 575 def get_all_comments(self, repo_id, revision=None, pull_request=None,
571 576 include_drafts=True, count_only=False):
572 577 q = ChangesetComment.query()\
573 578 .filter(ChangesetComment.repo_id == repo_id)
574 579 if revision:
575 580 q = q.filter(ChangesetComment.revision == revision)
576 581 elif pull_request:
577 582 pull_request = self.__get_pull_request(pull_request)
578 583 q = q.filter(ChangesetComment.pull_request_id == pull_request.pull_request_id)
579 584 else:
580 585 raise Exception('Please specify commit or pull_request')
581 586 if not include_drafts:
582 587 q = q.filter(ChangesetComment.draft == false())
583 588 q = q.order_by(ChangesetComment.created_on)
584 589 if count_only:
585 590 return q.count()
586 591
587 592 return q.all()
588 593
589 594 def get_url(self, comment, request=None, permalink=False, anchor=None):
590 595 if not request:
591 596 request = get_current_request()
592 597
593 598 comment = self.__get_commit_comment(comment)
594 599 if anchor is None:
595 600 anchor = 'comment-{}'.format(comment.comment_id)
596 601
597 602 if comment.pull_request:
598 603 pull_request = comment.pull_request
599 604 if permalink:
600 605 return request.route_url(
601 606 'pull_requests_global',
602 607 pull_request_id=pull_request.pull_request_id,
603 608 _anchor=anchor)
604 609 else:
605 610 return request.route_url(
606 611 'pullrequest_show',
607 612 repo_name=safe_str(pull_request.target_repo.repo_name),
608 613 pull_request_id=pull_request.pull_request_id,
609 614 _anchor=anchor)
610 615
611 616 else:
612 617 repo = comment.repo
613 618 commit_id = comment.revision
614 619
615 620 if permalink:
616 621 return request.route_url(
617 622 'repo_commit', repo_name=safe_str(repo.repo_id),
618 623 commit_id=commit_id,
619 624 _anchor=anchor)
620 625
621 626 else:
622 627 return request.route_url(
623 628 'repo_commit', repo_name=safe_str(repo.repo_name),
624 629 commit_id=commit_id,
625 630 _anchor=anchor)
626 631
627 632 def get_comments(self, repo_id, revision=None, pull_request=None):
628 633 """
629 634 Gets main comments based on revision or pull_request_id
630 635
631 636 :param repo_id:
632 637 :param revision:
633 638 :param pull_request:
634 639 """
635 640
636 641 q = ChangesetComment.query()\
637 642 .filter(ChangesetComment.repo_id == repo_id)\
638 643 .filter(ChangesetComment.line_no == None)\
639 644 .filter(ChangesetComment.f_path == None)
640 645 if revision:
641 646 q = q.filter(ChangesetComment.revision == revision)
642 647 elif pull_request:
643 648 pull_request = self.__get_pull_request(pull_request)
644 649 q = q.filter(ChangesetComment.pull_request == pull_request)
645 650 else:
646 651 raise Exception('Please specify commit or pull_request')
647 652 q = q.order_by(ChangesetComment.created_on)
648 653 return q.all()
649 654
650 655 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
651 656 q = self._get_inline_comments_query(repo_id, revision, pull_request)
652 657 return self._group_comments_by_path_and_line_number(q)
653 658
654 659 def get_inline_comments_as_list(self, inline_comments, skip_outdated=True,
655 660 version=None):
656 661 inline_comms = []
657 662 for fname, per_line_comments in inline_comments.iteritems():
658 663 for lno, comments in per_line_comments.iteritems():
659 664 for comm in comments:
660 665 if not comm.outdated_at_version(version) and skip_outdated:
661 666 inline_comms.append(comm)
662 667
663 668 return inline_comms
664 669
665 670 def get_outdated_comments(self, repo_id, pull_request):
666 671 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
667 672 # of a pull request.
668 673 q = self._all_inline_comments_of_pull_request(pull_request)
669 674 q = q.filter(
670 675 ChangesetComment.display_state ==
671 676 ChangesetComment.COMMENT_OUTDATED
672 677 ).order_by(ChangesetComment.comment_id.asc())
673 678
674 679 return self._group_comments_by_path_and_line_number(q)
675 680
676 681 def _get_inline_comments_query(self, repo_id, revision, pull_request):
677 682 # TODO: johbo: Split this into two methods: One for PR and one for
678 683 # commit.
679 684 if revision:
680 685 q = Session().query(ChangesetComment).filter(
681 686 ChangesetComment.repo_id == repo_id,
682 687 ChangesetComment.line_no != null(),
683 688 ChangesetComment.f_path != null(),
684 689 ChangesetComment.revision == revision)
685 690
686 691 elif pull_request:
687 692 pull_request = self.__get_pull_request(pull_request)
688 693 if not CommentsModel.use_outdated_comments(pull_request):
689 694 q = self._visible_inline_comments_of_pull_request(pull_request)
690 695 else:
691 696 q = self._all_inline_comments_of_pull_request(pull_request)
692 697
693 698 else:
694 699 raise Exception('Please specify commit or pull_request_id')
695 700 q = q.order_by(ChangesetComment.comment_id.asc())
696 701 return q
697 702
698 703 def _group_comments_by_path_and_line_number(self, q):
699 704 comments = q.all()
700 705 paths = collections.defaultdict(lambda: collections.defaultdict(list))
701 706 for co in comments:
702 707 paths[co.f_path][co.line_no].append(co)
703 708 return paths
704 709
705 710 @classmethod
706 711 def needed_extra_diff_context(cls):
707 712 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
708 713
709 714 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
710 715 if not CommentsModel.use_outdated_comments(pull_request):
711 716 return
712 717
713 718 comments = self._visible_inline_comments_of_pull_request(pull_request)
714 719 comments_to_outdate = comments.all()
715 720
716 721 for comment in comments_to_outdate:
717 722 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
718 723
719 724 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
720 725 diff_line = _parse_comment_line_number(comment.line_no)
721 726
722 727 try:
723 728 old_context = old_diff_proc.get_context_of_line(
724 729 path=comment.f_path, diff_line=diff_line)
725 730 new_context = new_diff_proc.get_context_of_line(
726 731 path=comment.f_path, diff_line=diff_line)
727 732 except (diffs.LineNotInDiffException,
728 733 diffs.FileNotInDiffException):
729 734 if not comment.draft:
730 735 comment.display_state = ChangesetComment.COMMENT_OUTDATED
731 736 return
732 737
733 738 if old_context == new_context:
734 739 return
735 740
736 741 if self._should_relocate_diff_line(diff_line):
737 742 new_diff_lines = new_diff_proc.find_context(
738 743 path=comment.f_path, context=old_context,
739 744 offset=self.DIFF_CONTEXT_BEFORE)
740 745 if not new_diff_lines and not comment.draft:
741 746 comment.display_state = ChangesetComment.COMMENT_OUTDATED
742 747 else:
743 748 new_diff_line = self._choose_closest_diff_line(
744 749 diff_line, new_diff_lines)
745 750 comment.line_no = _diff_to_comment_line_number(new_diff_line)
746 751 else:
747 752 if not comment.draft:
748 753 comment.display_state = ChangesetComment.COMMENT_OUTDATED
749 754
750 755 def _should_relocate_diff_line(self, diff_line):
751 756 """
752 757 Checks if relocation shall be tried for the given `diff_line`.
753 758
754 759 If a comment points into the first lines, then we can have a situation
755 760 that after an update another line has been added on top. In this case
756 761 we would find the context still and move the comment around. This
757 762 would be wrong.
758 763 """
759 764 should_relocate = (
760 765 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
761 766 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
762 767 return should_relocate
763 768
764 769 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
765 770 candidate = new_diff_lines[0]
766 771 best_delta = _diff_line_delta(diff_line, candidate)
767 772 for new_diff_line in new_diff_lines[1:]:
768 773 delta = _diff_line_delta(diff_line, new_diff_line)
769 774 if delta < best_delta:
770 775 candidate = new_diff_line
771 776 best_delta = delta
772 777 return candidate
773 778
774 779 def _visible_inline_comments_of_pull_request(self, pull_request):
775 780 comments = self._all_inline_comments_of_pull_request(pull_request)
776 781 comments = comments.filter(
777 782 coalesce(ChangesetComment.display_state, '') !=
778 783 ChangesetComment.COMMENT_OUTDATED)
779 784 return comments
780 785
781 786 def _all_inline_comments_of_pull_request(self, pull_request):
782 787 comments = Session().query(ChangesetComment)\
783 788 .filter(ChangesetComment.line_no != None)\
784 789 .filter(ChangesetComment.f_path != None)\
785 790 .filter(ChangesetComment.pull_request == pull_request)
786 791 return comments
787 792
788 793 def _all_general_comments_of_pull_request(self, pull_request):
789 794 comments = Session().query(ChangesetComment)\
790 795 .filter(ChangesetComment.line_no == None)\
791 796 .filter(ChangesetComment.f_path == None)\
792 797 .filter(ChangesetComment.pull_request == pull_request)
793 798
794 799 return comments
795 800
796 801 @staticmethod
797 802 def use_outdated_comments(pull_request):
798 803 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
799 804 settings = settings_model.get_general_settings()
800 805 return settings.get('rhodecode_use_outdated_comments', False)
801 806
802 807 def trigger_commit_comment_hook(self, repo, user, action, data=None):
803 808 repo = self._get_repo(repo)
804 809 target_scm = repo.scm_instance()
805 810 if action == 'create':
806 811 trigger_hook = hooks_utils.trigger_comment_commit_hooks
807 812 elif action == 'edit':
808 813 trigger_hook = hooks_utils.trigger_comment_commit_edit_hooks
809 814 else:
810 815 return
811 816
812 817 log.debug('Handling repo %s trigger_commit_comment_hook with action %s: %s',
813 818 repo, action, trigger_hook)
814 819 trigger_hook(
815 820 username=user.username,
816 821 repo_name=repo.repo_name,
817 822 repo_type=target_scm.alias,
818 823 repo=repo,
819 824 data=data)
820 825
821 826
822 827 def _parse_comment_line_number(line_no):
823 828 """
824 829 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
825 830 """
826 831 old_line = None
827 832 new_line = None
828 833 if line_no.startswith('o'):
829 834 old_line = int(line_no[1:])
830 835 elif line_no.startswith('n'):
831 836 new_line = int(line_no[1:])
832 837 else:
833 838 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
834 839 return diffs.DiffLineNumber(old_line, new_line)
835 840
836 841
837 842 def _diff_to_comment_line_number(diff_line):
838 843 if diff_line.new is not None:
839 844 return u'n{}'.format(diff_line.new)
840 845 elif diff_line.old is not None:
841 846 return u'o{}'.format(diff_line.old)
842 847 return u''
843 848
844 849
845 850 def _diff_line_delta(a, b):
846 851 if None not in (a.new, b.new):
847 852 return abs(a.new - b.new)
848 853 elif None not in (a.old, b.old):
849 854 return abs(a.old - b.old)
850 855 else:
851 856 raise ValueError(
852 857 "Cannot compute delta between {} and {}".format(a, b))
@@ -1,5830 +1,5836 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database Models for RhodeCode Enterprise
23 23 """
24 24
25 25 import re
26 26 import os
27 27 import time
28 28 import string
29 29 import hashlib
30 30 import logging
31 31 import datetime
32 32 import uuid
33 33 import warnings
34 34 import ipaddress
35 35 import functools
36 36 import traceback
37 37 import collections
38 38
39 39 from sqlalchemy import (
40 40 or_, and_, not_, func, cast, TypeDecorator, event,
41 41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 43 Text, Float, PickleType, BigInteger)
44 44 from sqlalchemy.sql.expression import true, false, case
45 45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 46 from sqlalchemy.orm import (
47 47 relationship, joinedload, class_mapper, validates, aliased)
48 48 from sqlalchemy.ext.declarative import declared_attr
49 49 from sqlalchemy.ext.hybrid import hybrid_property
50 50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 51 from sqlalchemy.dialects.mysql import LONGTEXT
52 52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 53 from pyramid import compat
54 54 from pyramid.threadlocal import get_current_request
55 55 from webhelpers2.text import remove_formatting
56 56
57 57 from rhodecode.translation import _
58 58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 59 from rhodecode.lib.vcs.backends.base import (
60 60 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
61 61 from rhodecode.lib.utils2 import (
62 62 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
63 63 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
64 64 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
65 65 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
66 66 JsonRaw
67 67 from rhodecode.lib.ext_json import json
68 68 from rhodecode.lib.caching_query import FromCache
69 69 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
70 70 from rhodecode.lib.encrypt2 import Encryptor
71 71 from rhodecode.lib.exceptions import (
72 72 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
73 73 from rhodecode.model.meta import Base, Session
74 74
75 75 URL_SEP = '/'
76 76 log = logging.getLogger(__name__)
77 77
78 78 # =============================================================================
79 79 # BASE CLASSES
80 80 # =============================================================================
81 81
82 82 # this is propagated from .ini file rhodecode.encrypted_values.secret or
83 83 # beaker.session.secret if first is not set.
84 84 # and initialized at environment.py
85 85 ENCRYPTION_KEY = None
86 86
87 87 # used to sort permissions by types, '#' used here is not allowed to be in
88 88 # usernames, and it's very early in sorted string.printable table.
89 89 PERMISSION_TYPE_SORT = {
90 90 'admin': '####',
91 91 'write': '###',
92 92 'read': '##',
93 93 'none': '#',
94 94 }
95 95
96 96
97 97 def display_user_sort(obj):
98 98 """
99 99 Sort function used to sort permissions in .permissions() function of
100 100 Repository, RepoGroup, UserGroup. Also it put the default user in front
101 101 of all other resources
102 102 """
103 103
104 104 if obj.username == User.DEFAULT_USER:
105 105 return '#####'
106 106 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
107 107 extra_sort_num = '1' # default
108 108
109 109 # NOTE(dan): inactive duplicates goes last
110 110 if getattr(obj, 'duplicate_perm', None):
111 111 extra_sort_num = '9'
112 112 return prefix + extra_sort_num + obj.username
113 113
114 114
115 115 def display_user_group_sort(obj):
116 116 """
117 117 Sort function used to sort permissions in .permissions() function of
118 118 Repository, RepoGroup, UserGroup. Also it put the default user in front
119 119 of all other resources
120 120 """
121 121
122 122 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
123 123 return prefix + obj.users_group_name
124 124
125 125
126 126 def _hash_key(k):
127 127 return sha1_safe(k)
128 128
129 129
130 130 def in_filter_generator(qry, items, limit=500):
131 131 """
132 132 Splits IN() into multiple with OR
133 133 e.g.::
134 134 cnt = Repository.query().filter(
135 135 or_(
136 136 *in_filter_generator(Repository.repo_id, range(100000))
137 137 )).count()
138 138 """
139 139 if not items:
140 140 # empty list will cause empty query which might cause security issues
141 141 # this can lead to hidden unpleasant results
142 142 items = [-1]
143 143
144 144 parts = []
145 145 for chunk in xrange(0, len(items), limit):
146 146 parts.append(
147 147 qry.in_(items[chunk: chunk + limit])
148 148 )
149 149
150 150 return parts
151 151
152 152
153 153 base_table_args = {
154 154 'extend_existing': True,
155 155 'mysql_engine': 'InnoDB',
156 156 'mysql_charset': 'utf8',
157 157 'sqlite_autoincrement': True
158 158 }
159 159
160 160
161 161 class EncryptedTextValue(TypeDecorator):
162 162 """
163 163 Special column for encrypted long text data, use like::
164 164
165 165 value = Column("encrypted_value", EncryptedValue(), nullable=False)
166 166
167 167 This column is intelligent so if value is in unencrypted form it return
168 168 unencrypted form, but on save it always encrypts
169 169 """
170 170 impl = Text
171 171
172 172 def process_bind_param(self, value, dialect):
173 173 """
174 174 Setter for storing value
175 175 """
176 176 import rhodecode
177 177 if not value:
178 178 return value
179 179
180 180 # protect against double encrypting if values is already encrypted
181 181 if value.startswith('enc$aes$') \
182 182 or value.startswith('enc$aes_hmac$') \
183 183 or value.startswith('enc2$'):
184 184 raise ValueError('value needs to be in unencrypted format, '
185 185 'ie. not starting with enc$ or enc2$')
186 186
187 187 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
188 188 if algo == 'aes':
189 189 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
190 190 elif algo == 'fernet':
191 191 return Encryptor(ENCRYPTION_KEY).encrypt(value)
192 192 else:
193 193 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
194 194
195 195 def process_result_value(self, value, dialect):
196 196 """
197 197 Getter for retrieving value
198 198 """
199 199
200 200 import rhodecode
201 201 if not value:
202 202 return value
203 203
204 204 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
205 205 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
206 206 if algo == 'aes':
207 207 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
208 208 elif algo == 'fernet':
209 209 return Encryptor(ENCRYPTION_KEY).decrypt(value)
210 210 else:
211 211 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
212 212 return decrypted_data
213 213
214 214
215 215 class BaseModel(object):
216 216 """
217 217 Base Model for all classes
218 218 """
219 219
220 220 @classmethod
221 221 def _get_keys(cls):
222 222 """return column names for this model """
223 223 return class_mapper(cls).c.keys()
224 224
225 225 def get_dict(self):
226 226 """
227 227 return dict with keys and values corresponding
228 228 to this model data """
229 229
230 230 d = {}
231 231 for k in self._get_keys():
232 232 d[k] = getattr(self, k)
233 233
234 234 # also use __json__() if present to get additional fields
235 235 _json_attr = getattr(self, '__json__', None)
236 236 if _json_attr:
237 237 # update with attributes from __json__
238 238 if callable(_json_attr):
239 239 _json_attr = _json_attr()
240 240 for k, val in _json_attr.iteritems():
241 241 d[k] = val
242 242 return d
243 243
244 244 def get_appstruct(self):
245 245 """return list with keys and values tuples corresponding
246 246 to this model data """
247 247
248 248 lst = []
249 249 for k in self._get_keys():
250 250 lst.append((k, getattr(self, k),))
251 251 return lst
252 252
253 253 def populate_obj(self, populate_dict):
254 254 """populate model with data from given populate_dict"""
255 255
256 256 for k in self._get_keys():
257 257 if k in populate_dict:
258 258 setattr(self, k, populate_dict[k])
259 259
260 260 @classmethod
261 261 def query(cls):
262 262 return Session().query(cls)
263 263
264 264 @classmethod
265 265 def get(cls, id_):
266 266 if id_:
267 267 return cls.query().get(id_)
268 268
269 269 @classmethod
270 270 def get_or_404(cls, id_):
271 271 from pyramid.httpexceptions import HTTPNotFound
272 272
273 273 try:
274 274 id_ = int(id_)
275 275 except (TypeError, ValueError):
276 276 raise HTTPNotFound()
277 277
278 278 res = cls.query().get(id_)
279 279 if not res:
280 280 raise HTTPNotFound()
281 281 return res
282 282
283 283 @classmethod
284 284 def getAll(cls):
285 285 # deprecated and left for backward compatibility
286 286 return cls.get_all()
287 287
288 288 @classmethod
289 289 def get_all(cls):
290 290 return cls.query().all()
291 291
292 292 @classmethod
293 293 def delete(cls, id_):
294 294 obj = cls.query().get(id_)
295 295 Session().delete(obj)
296 296
297 297 @classmethod
298 298 def identity_cache(cls, session, attr_name, value):
299 299 exist_in_session = []
300 300 for (item_cls, pkey), instance in session.identity_map.items():
301 301 if cls == item_cls and getattr(instance, attr_name) == value:
302 302 exist_in_session.append(instance)
303 303 if exist_in_session:
304 304 if len(exist_in_session) == 1:
305 305 return exist_in_session[0]
306 306 log.exception(
307 307 'multiple objects with attr %s and '
308 308 'value %s found with same name: %r',
309 309 attr_name, value, exist_in_session)
310 310
311 311 def __repr__(self):
312 312 if hasattr(self, '__unicode__'):
313 313 # python repr needs to return str
314 314 try:
315 315 return safe_str(self.__unicode__())
316 316 except UnicodeDecodeError:
317 317 pass
318 318 return '<DB:%s>' % (self.__class__.__name__)
319 319
320 320
321 321 class RhodeCodeSetting(Base, BaseModel):
322 322 __tablename__ = 'rhodecode_settings'
323 323 __table_args__ = (
324 324 UniqueConstraint('app_settings_name'),
325 325 base_table_args
326 326 )
327 327
328 328 SETTINGS_TYPES = {
329 329 'str': safe_str,
330 330 'int': safe_int,
331 331 'unicode': safe_unicode,
332 332 'bool': str2bool,
333 333 'list': functools.partial(aslist, sep=',')
334 334 }
335 335 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
336 336 GLOBAL_CONF_KEY = 'app_settings'
337 337
338 338 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
339 339 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
340 340 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
341 341 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
342 342
343 343 def __init__(self, key='', val='', type='unicode'):
344 344 self.app_settings_name = key
345 345 self.app_settings_type = type
346 346 self.app_settings_value = val
347 347
348 348 @validates('_app_settings_value')
349 349 def validate_settings_value(self, key, val):
350 350 assert type(val) == unicode
351 351 return val
352 352
353 353 @hybrid_property
354 354 def app_settings_value(self):
355 355 v = self._app_settings_value
356 356 _type = self.app_settings_type
357 357 if _type:
358 358 _type = self.app_settings_type.split('.')[0]
359 359 # decode the encrypted value
360 360 if 'encrypted' in self.app_settings_type:
361 361 cipher = EncryptedTextValue()
362 362 v = safe_unicode(cipher.process_result_value(v, None))
363 363
364 364 converter = self.SETTINGS_TYPES.get(_type) or \
365 365 self.SETTINGS_TYPES['unicode']
366 366 return converter(v)
367 367
368 368 @app_settings_value.setter
369 369 def app_settings_value(self, val):
370 370 """
371 371 Setter that will always make sure we use unicode in app_settings_value
372 372
373 373 :param val:
374 374 """
375 375 val = safe_unicode(val)
376 376 # encode the encrypted value
377 377 if 'encrypted' in self.app_settings_type:
378 378 cipher = EncryptedTextValue()
379 379 val = safe_unicode(cipher.process_bind_param(val, None))
380 380 self._app_settings_value = val
381 381
382 382 @hybrid_property
383 383 def app_settings_type(self):
384 384 return self._app_settings_type
385 385
386 386 @app_settings_type.setter
387 387 def app_settings_type(self, val):
388 388 if val.split('.')[0] not in self.SETTINGS_TYPES:
389 389 raise Exception('type must be one of %s got %s'
390 390 % (self.SETTINGS_TYPES.keys(), val))
391 391 self._app_settings_type = val
392 392
393 393 @classmethod
394 394 def get_by_prefix(cls, prefix):
395 395 return RhodeCodeSetting.query()\
396 396 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
397 397 .all()
398 398
399 399 def __unicode__(self):
400 400 return u"<%s('%s:%s[%s]')>" % (
401 401 self.__class__.__name__,
402 402 self.app_settings_name, self.app_settings_value,
403 403 self.app_settings_type
404 404 )
405 405
406 406
407 407 class RhodeCodeUi(Base, BaseModel):
408 408 __tablename__ = 'rhodecode_ui'
409 409 __table_args__ = (
410 410 UniqueConstraint('ui_key'),
411 411 base_table_args
412 412 )
413 413
414 414 HOOK_REPO_SIZE = 'changegroup.repo_size'
415 415 # HG
416 416 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
417 417 HOOK_PULL = 'outgoing.pull_logger'
418 418 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
419 419 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
420 420 HOOK_PUSH = 'changegroup.push_logger'
421 421 HOOK_PUSH_KEY = 'pushkey.key_push'
422 422
423 423 HOOKS_BUILTIN = [
424 424 HOOK_PRE_PULL,
425 425 HOOK_PULL,
426 426 HOOK_PRE_PUSH,
427 427 HOOK_PRETX_PUSH,
428 428 HOOK_PUSH,
429 429 HOOK_PUSH_KEY,
430 430 ]
431 431
432 432 # TODO: johbo: Unify way how hooks are configured for git and hg,
433 433 # git part is currently hardcoded.
434 434
435 435 # SVN PATTERNS
436 436 SVN_BRANCH_ID = 'vcs_svn_branch'
437 437 SVN_TAG_ID = 'vcs_svn_tag'
438 438
439 439 ui_id = Column(
440 440 "ui_id", Integer(), nullable=False, unique=True, default=None,
441 441 primary_key=True)
442 442 ui_section = Column(
443 443 "ui_section", String(255), nullable=True, unique=None, default=None)
444 444 ui_key = Column(
445 445 "ui_key", String(255), nullable=True, unique=None, default=None)
446 446 ui_value = Column(
447 447 "ui_value", String(255), nullable=True, unique=None, default=None)
448 448 ui_active = Column(
449 449 "ui_active", Boolean(), nullable=True, unique=None, default=True)
450 450
451 451 def __repr__(self):
452 452 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
453 453 self.ui_key, self.ui_value)
454 454
455 455
456 456 class RepoRhodeCodeSetting(Base, BaseModel):
457 457 __tablename__ = 'repo_rhodecode_settings'
458 458 __table_args__ = (
459 459 UniqueConstraint(
460 460 'app_settings_name', 'repository_id',
461 461 name='uq_repo_rhodecode_setting_name_repo_id'),
462 462 base_table_args
463 463 )
464 464
465 465 repository_id = Column(
466 466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 467 nullable=False)
468 468 app_settings_id = Column(
469 469 "app_settings_id", Integer(), nullable=False, unique=True,
470 470 default=None, primary_key=True)
471 471 app_settings_name = Column(
472 472 "app_settings_name", String(255), nullable=True, unique=None,
473 473 default=None)
474 474 _app_settings_value = Column(
475 475 "app_settings_value", String(4096), nullable=True, unique=None,
476 476 default=None)
477 477 _app_settings_type = Column(
478 478 "app_settings_type", String(255), nullable=True, unique=None,
479 479 default=None)
480 480
481 481 repository = relationship('Repository')
482 482
483 483 def __init__(self, repository_id, key='', val='', type='unicode'):
484 484 self.repository_id = repository_id
485 485 self.app_settings_name = key
486 486 self.app_settings_type = type
487 487 self.app_settings_value = val
488 488
489 489 @validates('_app_settings_value')
490 490 def validate_settings_value(self, key, val):
491 491 assert type(val) == unicode
492 492 return val
493 493
494 494 @hybrid_property
495 495 def app_settings_value(self):
496 496 v = self._app_settings_value
497 497 type_ = self.app_settings_type
498 498 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
499 499 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
500 500 return converter(v)
501 501
502 502 @app_settings_value.setter
503 503 def app_settings_value(self, val):
504 504 """
505 505 Setter that will always make sure we use unicode in app_settings_value
506 506
507 507 :param val:
508 508 """
509 509 self._app_settings_value = safe_unicode(val)
510 510
511 511 @hybrid_property
512 512 def app_settings_type(self):
513 513 return self._app_settings_type
514 514
515 515 @app_settings_type.setter
516 516 def app_settings_type(self, val):
517 517 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
518 518 if val not in SETTINGS_TYPES:
519 519 raise Exception('type must be one of %s got %s'
520 520 % (SETTINGS_TYPES.keys(), val))
521 521 self._app_settings_type = val
522 522
523 523 def __unicode__(self):
524 524 return u"<%s('%s:%s:%s[%s]')>" % (
525 525 self.__class__.__name__, self.repository.repo_name,
526 526 self.app_settings_name, self.app_settings_value,
527 527 self.app_settings_type
528 528 )
529 529
530 530
531 531 class RepoRhodeCodeUi(Base, BaseModel):
532 532 __tablename__ = 'repo_rhodecode_ui'
533 533 __table_args__ = (
534 534 UniqueConstraint(
535 535 'repository_id', 'ui_section', 'ui_key',
536 536 name='uq_repo_rhodecode_ui_repository_id_section_key'),
537 537 base_table_args
538 538 )
539 539
540 540 repository_id = Column(
541 541 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
542 542 nullable=False)
543 543 ui_id = Column(
544 544 "ui_id", Integer(), nullable=False, unique=True, default=None,
545 545 primary_key=True)
546 546 ui_section = Column(
547 547 "ui_section", String(255), nullable=True, unique=None, default=None)
548 548 ui_key = Column(
549 549 "ui_key", String(255), nullable=True, unique=None, default=None)
550 550 ui_value = Column(
551 551 "ui_value", String(255), nullable=True, unique=None, default=None)
552 552 ui_active = Column(
553 553 "ui_active", Boolean(), nullable=True, unique=None, default=True)
554 554
555 555 repository = relationship('Repository')
556 556
557 557 def __repr__(self):
558 558 return '<%s[%s:%s]%s=>%s]>' % (
559 559 self.__class__.__name__, self.repository.repo_name,
560 560 self.ui_section, self.ui_key, self.ui_value)
561 561
562 562
563 563 class User(Base, BaseModel):
564 564 __tablename__ = 'users'
565 565 __table_args__ = (
566 566 UniqueConstraint('username'), UniqueConstraint('email'),
567 567 Index('u_username_idx', 'username'),
568 568 Index('u_email_idx', 'email'),
569 569 base_table_args
570 570 )
571 571
572 572 DEFAULT_USER = 'default'
573 573 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
574 574 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
575 575
576 576 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
577 577 username = Column("username", String(255), nullable=True, unique=None, default=None)
578 578 password = Column("password", String(255), nullable=True, unique=None, default=None)
579 579 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
580 580 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
581 581 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
582 582 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
583 583 _email = Column("email", String(255), nullable=True, unique=None, default=None)
584 584 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
585 585 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
586 586 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
587 587
588 588 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
589 589 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
590 590 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
591 591 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
592 592 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
593 593 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
594 594
595 595 user_log = relationship('UserLog')
596 596 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
597 597
598 598 repositories = relationship('Repository')
599 599 repository_groups = relationship('RepoGroup')
600 600 user_groups = relationship('UserGroup')
601 601
602 602 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
603 603 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
604 604
605 605 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
606 606 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
607 607 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
608 608
609 609 group_member = relationship('UserGroupMember', cascade='all')
610 610
611 611 notifications = relationship('UserNotification', cascade='all')
612 612 # notifications assigned to this user
613 613 user_created_notifications = relationship('Notification', cascade='all')
614 614 # comments created by this user
615 615 user_comments = relationship('ChangesetComment', cascade='all')
616 616 # user profile extra info
617 617 user_emails = relationship('UserEmailMap', cascade='all')
618 618 user_ip_map = relationship('UserIpMap', cascade='all')
619 619 user_auth_tokens = relationship('UserApiKeys', cascade='all')
620 620 user_ssh_keys = relationship('UserSshKeys', cascade='all')
621 621
622 622 # gists
623 623 user_gists = relationship('Gist', cascade='all')
624 624 # user pull requests
625 625 user_pull_requests = relationship('PullRequest', cascade='all')
626 626
627 627 # external identities
628 628 external_identities = relationship(
629 629 'ExternalIdentity',
630 630 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
631 631 cascade='all')
632 632 # review rules
633 633 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
634 634
635 635 # artifacts owned
636 636 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
637 637
638 638 # no cascade, set NULL
639 639 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
640 640
641 641 def __unicode__(self):
642 642 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
643 643 self.user_id, self.username)
644 644
645 645 @hybrid_property
646 646 def email(self):
647 647 return self._email
648 648
649 649 @email.setter
650 650 def email(self, val):
651 651 self._email = val.lower() if val else None
652 652
653 653 @hybrid_property
654 654 def first_name(self):
655 655 from rhodecode.lib import helpers as h
656 656 if self.name:
657 657 return h.escape(self.name)
658 658 return self.name
659 659
660 660 @hybrid_property
661 661 def last_name(self):
662 662 from rhodecode.lib import helpers as h
663 663 if self.lastname:
664 664 return h.escape(self.lastname)
665 665 return self.lastname
666 666
667 667 @hybrid_property
668 668 def api_key(self):
669 669 """
670 670 Fetch if exist an auth-token with role ALL connected to this user
671 671 """
672 672 user_auth_token = UserApiKeys.query()\
673 673 .filter(UserApiKeys.user_id == self.user_id)\
674 674 .filter(or_(UserApiKeys.expires == -1,
675 675 UserApiKeys.expires >= time.time()))\
676 676 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
677 677 if user_auth_token:
678 678 user_auth_token = user_auth_token.api_key
679 679
680 680 return user_auth_token
681 681
682 682 @api_key.setter
683 683 def api_key(self, val):
684 684 # don't allow to set API key this is deprecated for now
685 685 self._api_key = None
686 686
687 687 @property
688 688 def reviewer_pull_requests(self):
689 689 return PullRequestReviewers.query() \
690 690 .options(joinedload(PullRequestReviewers.pull_request)) \
691 691 .filter(PullRequestReviewers.user_id == self.user_id) \
692 692 .all()
693 693
694 694 @property
695 695 def firstname(self):
696 696 # alias for future
697 697 return self.name
698 698
699 699 @property
700 700 def emails(self):
701 701 other = UserEmailMap.query()\
702 702 .filter(UserEmailMap.user == self) \
703 703 .order_by(UserEmailMap.email_id.asc()) \
704 704 .all()
705 705 return [self.email] + [x.email for x in other]
706 706
707 707 def emails_cached(self):
708 708 emails = UserEmailMap.query()\
709 709 .filter(UserEmailMap.user == self) \
710 710 .order_by(UserEmailMap.email_id.asc())
711 711
712 712 emails = emails.options(
713 713 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
714 714 )
715 715
716 716 return [self.email] + [x.email for x in emails]
717 717
718 718 @property
719 719 def auth_tokens(self):
720 720 auth_tokens = self.get_auth_tokens()
721 721 return [x.api_key for x in auth_tokens]
722 722
723 723 def get_auth_tokens(self):
724 724 return UserApiKeys.query()\
725 725 .filter(UserApiKeys.user == self)\
726 726 .order_by(UserApiKeys.user_api_key_id.asc())\
727 727 .all()
728 728
729 729 @LazyProperty
730 730 def feed_token(self):
731 731 return self.get_feed_token()
732 732
733 733 def get_feed_token(self, cache=True):
734 734 feed_tokens = UserApiKeys.query()\
735 735 .filter(UserApiKeys.user == self)\
736 736 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
737 737 if cache:
738 738 feed_tokens = feed_tokens.options(
739 739 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
740 740
741 741 feed_tokens = feed_tokens.all()
742 742 if feed_tokens:
743 743 return feed_tokens[0].api_key
744 744 return 'NO_FEED_TOKEN_AVAILABLE'
745 745
746 746 @LazyProperty
747 747 def artifact_token(self):
748 748 return self.get_artifact_token()
749 749
750 750 def get_artifact_token(self, cache=True):
751 751 artifacts_tokens = UserApiKeys.query()\
752 752 .filter(UserApiKeys.user == self) \
753 753 .filter(or_(UserApiKeys.expires == -1,
754 754 UserApiKeys.expires >= time.time())) \
755 755 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
756 756
757 757 if cache:
758 758 artifacts_tokens = artifacts_tokens.options(
759 759 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
760 760
761 761 artifacts_tokens = artifacts_tokens.all()
762 762 if artifacts_tokens:
763 763 return artifacts_tokens[0].api_key
764 764 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
765 765
766 766 def get_or_create_artifact_token(self):
767 767 artifacts_tokens = UserApiKeys.query()\
768 768 .filter(UserApiKeys.user == self) \
769 769 .filter(or_(UserApiKeys.expires == -1,
770 770 UserApiKeys.expires >= time.time())) \
771 771 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
772 772
773 773 artifacts_tokens = artifacts_tokens.all()
774 774 if artifacts_tokens:
775 775 return artifacts_tokens[0].api_key
776 776 else:
777 777 from rhodecode.model.auth_token import AuthTokenModel
778 778 artifact_token = AuthTokenModel().create(
779 779 self, 'auto-generated-artifact-token',
780 780 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
781 781 Session.commit()
782 782 return artifact_token.api_key
783 783
784 784 @classmethod
785 785 def get(cls, user_id, cache=False):
786 786 if not user_id:
787 787 return
788 788
789 789 user = cls.query()
790 790 if cache:
791 791 user = user.options(
792 792 FromCache("sql_cache_short", "get_users_%s" % user_id))
793 793 return user.get(user_id)
794 794
795 795 @classmethod
796 796 def extra_valid_auth_tokens(cls, user, role=None):
797 797 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
798 798 .filter(or_(UserApiKeys.expires == -1,
799 799 UserApiKeys.expires >= time.time()))
800 800 if role:
801 801 tokens = tokens.filter(or_(UserApiKeys.role == role,
802 802 UserApiKeys.role == UserApiKeys.ROLE_ALL))
803 803 return tokens.all()
804 804
805 805 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
806 806 from rhodecode.lib import auth
807 807
808 808 log.debug('Trying to authenticate user: %s via auth-token, '
809 809 'and roles: %s', self, roles)
810 810
811 811 if not auth_token:
812 812 return False
813 813
814 814 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
815 815 tokens_q = UserApiKeys.query()\
816 816 .filter(UserApiKeys.user_id == self.user_id)\
817 817 .filter(or_(UserApiKeys.expires == -1,
818 818 UserApiKeys.expires >= time.time()))
819 819
820 820 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
821 821
822 822 crypto_backend = auth.crypto_backend()
823 823 enc_token_map = {}
824 824 plain_token_map = {}
825 825 for token in tokens_q:
826 826 if token.api_key.startswith(crypto_backend.ENC_PREF):
827 827 enc_token_map[token.api_key] = token
828 828 else:
829 829 plain_token_map[token.api_key] = token
830 830 log.debug(
831 831 'Found %s plain and %s encrypted tokens to check for authentication for this user',
832 832 len(plain_token_map), len(enc_token_map))
833 833
834 834 # plain token match comes first
835 835 match = plain_token_map.get(auth_token)
836 836
837 837 # check encrypted tokens now
838 838 if not match:
839 839 for token_hash, token in enc_token_map.items():
840 840 # NOTE(marcink): this is expensive to calculate, but most secure
841 841 if crypto_backend.hash_check(auth_token, token_hash):
842 842 match = token
843 843 break
844 844
845 845 if match:
846 846 log.debug('Found matching token %s', match)
847 847 if match.repo_id:
848 848 log.debug('Found scope, checking for scope match of token %s', match)
849 849 if match.repo_id == scope_repo_id:
850 850 return True
851 851 else:
852 852 log.debug(
853 853 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
854 854 'and calling scope is:%s, skipping further checks',
855 855 match.repo, scope_repo_id)
856 856 return False
857 857 else:
858 858 return True
859 859
860 860 return False
861 861
862 862 @property
863 863 def ip_addresses(self):
864 864 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
865 865 return [x.ip_addr for x in ret]
866 866
867 867 @property
868 868 def username_and_name(self):
869 869 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
870 870
871 871 @property
872 872 def username_or_name_or_email(self):
873 873 full_name = self.full_name if self.full_name is not ' ' else None
874 874 return self.username or full_name or self.email
875 875
876 876 @property
877 877 def full_name(self):
878 878 return '%s %s' % (self.first_name, self.last_name)
879 879
880 880 @property
881 881 def full_name_or_username(self):
882 882 return ('%s %s' % (self.first_name, self.last_name)
883 883 if (self.first_name and self.last_name) else self.username)
884 884
885 885 @property
886 886 def full_contact(self):
887 887 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
888 888
889 889 @property
890 890 def short_contact(self):
891 891 return '%s %s' % (self.first_name, self.last_name)
892 892
893 893 @property
894 894 def is_admin(self):
895 895 return self.admin
896 896
897 897 @property
898 898 def language(self):
899 899 return self.user_data.get('language')
900 900
901 901 def AuthUser(self, **kwargs):
902 902 """
903 903 Returns instance of AuthUser for this user
904 904 """
905 905 from rhodecode.lib.auth import AuthUser
906 906 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
907 907
908 908 @hybrid_property
909 909 def user_data(self):
910 910 if not self._user_data:
911 911 return {}
912 912
913 913 try:
914 914 return json.loads(self._user_data)
915 915 except TypeError:
916 916 return {}
917 917
918 918 @user_data.setter
919 919 def user_data(self, val):
920 920 if not isinstance(val, dict):
921 921 raise Exception('user_data must be dict, got %s' % type(val))
922 922 try:
923 923 self._user_data = json.dumps(val)
924 924 except Exception:
925 925 log.error(traceback.format_exc())
926 926
927 927 @classmethod
928 928 def get_by_username(cls, username, case_insensitive=False,
929 929 cache=False, identity_cache=False):
930 930 session = Session()
931 931
932 932 if case_insensitive:
933 933 q = cls.query().filter(
934 934 func.lower(cls.username) == func.lower(username))
935 935 else:
936 936 q = cls.query().filter(cls.username == username)
937 937
938 938 if cache:
939 939 if identity_cache:
940 940 val = cls.identity_cache(session, 'username', username)
941 941 if val:
942 942 return val
943 943 else:
944 944 cache_key = "get_user_by_name_%s" % _hash_key(username)
945 945 q = q.options(
946 946 FromCache("sql_cache_short", cache_key))
947 947
948 948 return q.scalar()
949 949
950 950 @classmethod
951 951 def get_by_auth_token(cls, auth_token, cache=False):
952 952 q = UserApiKeys.query()\
953 953 .filter(UserApiKeys.api_key == auth_token)\
954 954 .filter(or_(UserApiKeys.expires == -1,
955 955 UserApiKeys.expires >= time.time()))
956 956 if cache:
957 957 q = q.options(
958 958 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
959 959
960 960 match = q.first()
961 961 if match:
962 962 return match.user
963 963
964 964 @classmethod
965 965 def get_by_email(cls, email, case_insensitive=False, cache=False):
966 966
967 967 if case_insensitive:
968 968 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
969 969
970 970 else:
971 971 q = cls.query().filter(cls.email == email)
972 972
973 973 email_key = _hash_key(email)
974 974 if cache:
975 975 q = q.options(
976 976 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
977 977
978 978 ret = q.scalar()
979 979 if ret is None:
980 980 q = UserEmailMap.query()
981 981 # try fetching in alternate email map
982 982 if case_insensitive:
983 983 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
984 984 else:
985 985 q = q.filter(UserEmailMap.email == email)
986 986 q = q.options(joinedload(UserEmailMap.user))
987 987 if cache:
988 988 q = q.options(
989 989 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
990 990 ret = getattr(q.scalar(), 'user', None)
991 991
992 992 return ret
993 993
994 994 @classmethod
995 995 def get_from_cs_author(cls, author):
996 996 """
997 997 Tries to get User objects out of commit author string
998 998
999 999 :param author:
1000 1000 """
1001 1001 from rhodecode.lib.helpers import email, author_name
1002 1002 # Valid email in the attribute passed, see if they're in the system
1003 1003 _email = email(author)
1004 1004 if _email:
1005 1005 user = cls.get_by_email(_email, case_insensitive=True)
1006 1006 if user:
1007 1007 return user
1008 1008 # Maybe we can match by username?
1009 1009 _author = author_name(author)
1010 1010 user = cls.get_by_username(_author, case_insensitive=True)
1011 1011 if user:
1012 1012 return user
1013 1013
1014 1014 def update_userdata(self, **kwargs):
1015 1015 usr = self
1016 1016 old = usr.user_data
1017 1017 old.update(**kwargs)
1018 1018 usr.user_data = old
1019 1019 Session().add(usr)
1020 1020 log.debug('updated userdata with %s', kwargs)
1021 1021
1022 1022 def update_lastlogin(self):
1023 1023 """Update user lastlogin"""
1024 1024 self.last_login = datetime.datetime.now()
1025 1025 Session().add(self)
1026 1026 log.debug('updated user %s lastlogin', self.username)
1027 1027
1028 1028 def update_password(self, new_password):
1029 1029 from rhodecode.lib.auth import get_crypt_password
1030 1030
1031 1031 self.password = get_crypt_password(new_password)
1032 1032 Session().add(self)
1033 1033
1034 1034 @classmethod
1035 1035 def get_first_super_admin(cls):
1036 1036 user = User.query()\
1037 1037 .filter(User.admin == true()) \
1038 1038 .order_by(User.user_id.asc()) \
1039 1039 .first()
1040 1040
1041 1041 if user is None:
1042 1042 raise Exception('FATAL: Missing administrative account!')
1043 1043 return user
1044 1044
1045 1045 @classmethod
1046 1046 def get_all_super_admins(cls, only_active=False):
1047 1047 """
1048 1048 Returns all admin accounts sorted by username
1049 1049 """
1050 1050 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1051 1051 if only_active:
1052 1052 qry = qry.filter(User.active == true())
1053 1053 return qry.all()
1054 1054
1055 1055 @classmethod
1056 1056 def get_all_user_ids(cls, only_active=True):
1057 1057 """
1058 1058 Returns all users IDs
1059 1059 """
1060 1060 qry = Session().query(User.user_id)
1061 1061
1062 1062 if only_active:
1063 1063 qry = qry.filter(User.active == true())
1064 1064 return [x.user_id for x in qry]
1065 1065
1066 1066 @classmethod
1067 1067 def get_default_user(cls, cache=False, refresh=False):
1068 1068 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1069 1069 if user is None:
1070 1070 raise Exception('FATAL: Missing default account!')
1071 1071 if refresh:
1072 1072 # The default user might be based on outdated state which
1073 1073 # has been loaded from the cache.
1074 1074 # A call to refresh() ensures that the
1075 1075 # latest state from the database is used.
1076 1076 Session().refresh(user)
1077 1077 return user
1078 1078
1079 1079 @classmethod
1080 1080 def get_default_user_id(cls):
1081 1081 import rhodecode
1082 1082 return rhodecode.CONFIG['default_user_id']
1083 1083
1084 1084 def _get_default_perms(self, user, suffix=''):
1085 1085 from rhodecode.model.permission import PermissionModel
1086 1086 return PermissionModel().get_default_perms(user.user_perms, suffix)
1087 1087
1088 1088 def get_default_perms(self, suffix=''):
1089 1089 return self._get_default_perms(self, suffix)
1090 1090
1091 1091 def get_api_data(self, include_secrets=False, details='full'):
1092 1092 """
1093 1093 Common function for generating user related data for API
1094 1094
1095 1095 :param include_secrets: By default secrets in the API data will be replaced
1096 1096 by a placeholder value to prevent exposing this data by accident. In case
1097 1097 this data shall be exposed, set this flag to ``True``.
1098 1098
1099 1099 :param details: details can be 'basic|full' basic gives only a subset of
1100 1100 the available user information that includes user_id, name and emails.
1101 1101 """
1102 1102 user = self
1103 1103 user_data = self.user_data
1104 1104 data = {
1105 1105 'user_id': user.user_id,
1106 1106 'username': user.username,
1107 1107 'firstname': user.name,
1108 1108 'lastname': user.lastname,
1109 1109 'description': user.description,
1110 1110 'email': user.email,
1111 1111 'emails': user.emails,
1112 1112 }
1113 1113 if details == 'basic':
1114 1114 return data
1115 1115
1116 1116 auth_token_length = 40
1117 1117 auth_token_replacement = '*' * auth_token_length
1118 1118
1119 1119 extras = {
1120 1120 'auth_tokens': [auth_token_replacement],
1121 1121 'active': user.active,
1122 1122 'admin': user.admin,
1123 1123 'extern_type': user.extern_type,
1124 1124 'extern_name': user.extern_name,
1125 1125 'last_login': user.last_login,
1126 1126 'last_activity': user.last_activity,
1127 1127 'ip_addresses': user.ip_addresses,
1128 1128 'language': user_data.get('language')
1129 1129 }
1130 1130 data.update(extras)
1131 1131
1132 1132 if include_secrets:
1133 1133 data['auth_tokens'] = user.auth_tokens
1134 1134 return data
1135 1135
1136 1136 def __json__(self):
1137 1137 data = {
1138 1138 'full_name': self.full_name,
1139 1139 'full_name_or_username': self.full_name_or_username,
1140 1140 'short_contact': self.short_contact,
1141 1141 'full_contact': self.full_contact,
1142 1142 }
1143 1143 data.update(self.get_api_data())
1144 1144 return data
1145 1145
1146 1146
1147 1147 class UserApiKeys(Base, BaseModel):
1148 1148 __tablename__ = 'user_api_keys'
1149 1149 __table_args__ = (
1150 1150 Index('uak_api_key_idx', 'api_key'),
1151 1151 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1152 1152 base_table_args
1153 1153 )
1154 1154 __mapper_args__ = {}
1155 1155
1156 1156 # ApiKey role
1157 1157 ROLE_ALL = 'token_role_all'
1158 1158 ROLE_VCS = 'token_role_vcs'
1159 1159 ROLE_API = 'token_role_api'
1160 1160 ROLE_HTTP = 'token_role_http'
1161 1161 ROLE_FEED = 'token_role_feed'
1162 1162 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1163 1163 # The last one is ignored in the list as we only
1164 1164 # use it for one action, and cannot be created by users
1165 1165 ROLE_PASSWORD_RESET = 'token_password_reset'
1166 1166
1167 1167 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1168 1168
1169 1169 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1170 1170 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1171 1171 api_key = Column("api_key", String(255), nullable=False, unique=True)
1172 1172 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1173 1173 expires = Column('expires', Float(53), nullable=False)
1174 1174 role = Column('role', String(255), nullable=True)
1175 1175 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1176 1176
1177 1177 # scope columns
1178 1178 repo_id = Column(
1179 1179 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1180 1180 nullable=True, unique=None, default=None)
1181 1181 repo = relationship('Repository', lazy='joined')
1182 1182
1183 1183 repo_group_id = Column(
1184 1184 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1185 1185 nullable=True, unique=None, default=None)
1186 1186 repo_group = relationship('RepoGroup', lazy='joined')
1187 1187
1188 1188 user = relationship('User', lazy='joined')
1189 1189
1190 1190 def __unicode__(self):
1191 1191 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1192 1192
1193 1193 def __json__(self):
1194 1194 data = {
1195 1195 'auth_token': self.api_key,
1196 1196 'role': self.role,
1197 1197 'scope': self.scope_humanized,
1198 1198 'expired': self.expired
1199 1199 }
1200 1200 return data
1201 1201
1202 1202 def get_api_data(self, include_secrets=False):
1203 1203 data = self.__json__()
1204 1204 if include_secrets:
1205 1205 return data
1206 1206 else:
1207 1207 data['auth_token'] = self.token_obfuscated
1208 1208 return data
1209 1209
1210 1210 @hybrid_property
1211 1211 def description_safe(self):
1212 1212 from rhodecode.lib import helpers as h
1213 1213 return h.escape(self.description)
1214 1214
1215 1215 @property
1216 1216 def expired(self):
1217 1217 if self.expires == -1:
1218 1218 return False
1219 1219 return time.time() > self.expires
1220 1220
1221 1221 @classmethod
1222 1222 def _get_role_name(cls, role):
1223 1223 return {
1224 1224 cls.ROLE_ALL: _('all'),
1225 1225 cls.ROLE_HTTP: _('http/web interface'),
1226 1226 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1227 1227 cls.ROLE_API: _('api calls'),
1228 1228 cls.ROLE_FEED: _('feed access'),
1229 1229 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1230 1230 }.get(role, role)
1231 1231
1232 1232 @classmethod
1233 1233 def _get_role_description(cls, role):
1234 1234 return {
1235 1235 cls.ROLE_ALL: _('Token for all actions.'),
1236 1236 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1237 1237 'login using `api_access_controllers_whitelist` functionality.'),
1238 1238 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1239 1239 'Requires auth_token authentication plugin to be active. <br/>'
1240 1240 'Such Token should be used then instead of a password to '
1241 1241 'interact with a repository, and additionally can be '
1242 1242 'limited to single repository using repo scope.'),
1243 1243 cls.ROLE_API: _('Token limited to api calls.'),
1244 1244 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1245 1245 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1246 1246 }.get(role, role)
1247 1247
1248 1248 @property
1249 1249 def role_humanized(self):
1250 1250 return self._get_role_name(self.role)
1251 1251
1252 1252 def _get_scope(self):
1253 1253 if self.repo:
1254 1254 return 'Repository: {}'.format(self.repo.repo_name)
1255 1255 if self.repo_group:
1256 1256 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1257 1257 return 'Global'
1258 1258
1259 1259 @property
1260 1260 def scope_humanized(self):
1261 1261 return self._get_scope()
1262 1262
1263 1263 @property
1264 1264 def token_obfuscated(self):
1265 1265 if self.api_key:
1266 1266 return self.api_key[:4] + "****"
1267 1267
1268 1268
1269 1269 class UserEmailMap(Base, BaseModel):
1270 1270 __tablename__ = 'user_email_map'
1271 1271 __table_args__ = (
1272 1272 Index('uem_email_idx', 'email'),
1273 1273 UniqueConstraint('email'),
1274 1274 base_table_args
1275 1275 )
1276 1276 __mapper_args__ = {}
1277 1277
1278 1278 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1279 1279 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1280 1280 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1281 1281 user = relationship('User', lazy='joined')
1282 1282
1283 1283 @validates('_email')
1284 1284 def validate_email(self, key, email):
1285 1285 # check if this email is not main one
1286 1286 main_email = Session().query(User).filter(User.email == email).scalar()
1287 1287 if main_email is not None:
1288 1288 raise AttributeError('email %s is present is user table' % email)
1289 1289 return email
1290 1290
1291 1291 @hybrid_property
1292 1292 def email(self):
1293 1293 return self._email
1294 1294
1295 1295 @email.setter
1296 1296 def email(self, val):
1297 1297 self._email = val.lower() if val else None
1298 1298
1299 1299
1300 1300 class UserIpMap(Base, BaseModel):
1301 1301 __tablename__ = 'user_ip_map'
1302 1302 __table_args__ = (
1303 1303 UniqueConstraint('user_id', 'ip_addr'),
1304 1304 base_table_args
1305 1305 )
1306 1306 __mapper_args__ = {}
1307 1307
1308 1308 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1309 1309 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1310 1310 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1311 1311 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1312 1312 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1313 1313 user = relationship('User', lazy='joined')
1314 1314
1315 1315 @hybrid_property
1316 1316 def description_safe(self):
1317 1317 from rhodecode.lib import helpers as h
1318 1318 return h.escape(self.description)
1319 1319
1320 1320 @classmethod
1321 1321 def _get_ip_range(cls, ip_addr):
1322 1322 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1323 1323 return [str(net.network_address), str(net.broadcast_address)]
1324 1324
1325 1325 def __json__(self):
1326 1326 return {
1327 1327 'ip_addr': self.ip_addr,
1328 1328 'ip_range': self._get_ip_range(self.ip_addr),
1329 1329 }
1330 1330
1331 1331 def __unicode__(self):
1332 1332 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1333 1333 self.user_id, self.ip_addr)
1334 1334
1335 1335
1336 1336 class UserSshKeys(Base, BaseModel):
1337 1337 __tablename__ = 'user_ssh_keys'
1338 1338 __table_args__ = (
1339 1339 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1340 1340
1341 1341 UniqueConstraint('ssh_key_fingerprint'),
1342 1342
1343 1343 base_table_args
1344 1344 )
1345 1345 __mapper_args__ = {}
1346 1346
1347 1347 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1348 1348 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1349 1349 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1350 1350
1351 1351 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1352 1352
1353 1353 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1354 1354 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1355 1355 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1356 1356
1357 1357 user = relationship('User', lazy='joined')
1358 1358
1359 1359 def __json__(self):
1360 1360 data = {
1361 1361 'ssh_fingerprint': self.ssh_key_fingerprint,
1362 1362 'description': self.description,
1363 1363 'created_on': self.created_on
1364 1364 }
1365 1365 return data
1366 1366
1367 1367 def get_api_data(self):
1368 1368 data = self.__json__()
1369 1369 return data
1370 1370
1371 1371
1372 1372 class UserLog(Base, BaseModel):
1373 1373 __tablename__ = 'user_logs'
1374 1374 __table_args__ = (
1375 1375 base_table_args,
1376 1376 )
1377 1377
1378 1378 VERSION_1 = 'v1'
1379 1379 VERSION_2 = 'v2'
1380 1380 VERSIONS = [VERSION_1, VERSION_2]
1381 1381
1382 1382 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1383 1383 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1384 1384 username = Column("username", String(255), nullable=True, unique=None, default=None)
1385 1385 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1386 1386 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1387 1387 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1388 1388 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1389 1389 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1390 1390
1391 1391 version = Column("version", String(255), nullable=True, default=VERSION_1)
1392 1392 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1393 1393 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1394 1394
1395 1395 def __unicode__(self):
1396 1396 return u"<%s('id:%s:%s')>" % (
1397 1397 self.__class__.__name__, self.repository_name, self.action)
1398 1398
1399 1399 def __json__(self):
1400 1400 return {
1401 1401 'user_id': self.user_id,
1402 1402 'username': self.username,
1403 1403 'repository_id': self.repository_id,
1404 1404 'repository_name': self.repository_name,
1405 1405 'user_ip': self.user_ip,
1406 1406 'action_date': self.action_date,
1407 1407 'action': self.action,
1408 1408 }
1409 1409
1410 1410 @hybrid_property
1411 1411 def entry_id(self):
1412 1412 return self.user_log_id
1413 1413
1414 1414 @property
1415 1415 def action_as_day(self):
1416 1416 return datetime.date(*self.action_date.timetuple()[:3])
1417 1417
1418 1418 user = relationship('User')
1419 1419 repository = relationship('Repository', cascade='')
1420 1420
1421 1421
1422 1422 class UserGroup(Base, BaseModel):
1423 1423 __tablename__ = 'users_groups'
1424 1424 __table_args__ = (
1425 1425 base_table_args,
1426 1426 )
1427 1427
1428 1428 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1429 1429 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1430 1430 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1431 1431 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1432 1432 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1433 1433 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1434 1434 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1435 1435 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1436 1436
1437 1437 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1438 1438 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1439 1439 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1440 1440 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1441 1441 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1442 1442 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1443 1443
1444 1444 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1445 1445 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1446 1446
1447 1447 @classmethod
1448 1448 def _load_group_data(cls, column):
1449 1449 if not column:
1450 1450 return {}
1451 1451
1452 1452 try:
1453 1453 return json.loads(column) or {}
1454 1454 except TypeError:
1455 1455 return {}
1456 1456
1457 1457 @hybrid_property
1458 1458 def description_safe(self):
1459 1459 from rhodecode.lib import helpers as h
1460 1460 return h.escape(self.user_group_description)
1461 1461
1462 1462 @hybrid_property
1463 1463 def group_data(self):
1464 1464 return self._load_group_data(self._group_data)
1465 1465
1466 1466 @group_data.expression
1467 1467 def group_data(self, **kwargs):
1468 1468 return self._group_data
1469 1469
1470 1470 @group_data.setter
1471 1471 def group_data(self, val):
1472 1472 try:
1473 1473 self._group_data = json.dumps(val)
1474 1474 except Exception:
1475 1475 log.error(traceback.format_exc())
1476 1476
1477 1477 @classmethod
1478 1478 def _load_sync(cls, group_data):
1479 1479 if group_data:
1480 1480 return group_data.get('extern_type')
1481 1481
1482 1482 @property
1483 1483 def sync(self):
1484 1484 return self._load_sync(self.group_data)
1485 1485
1486 1486 def __unicode__(self):
1487 1487 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1488 1488 self.users_group_id,
1489 1489 self.users_group_name)
1490 1490
1491 1491 @classmethod
1492 1492 def get_by_group_name(cls, group_name, cache=False,
1493 1493 case_insensitive=False):
1494 1494 if case_insensitive:
1495 1495 q = cls.query().filter(func.lower(cls.users_group_name) ==
1496 1496 func.lower(group_name))
1497 1497
1498 1498 else:
1499 1499 q = cls.query().filter(cls.users_group_name == group_name)
1500 1500 if cache:
1501 1501 q = q.options(
1502 1502 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1503 1503 return q.scalar()
1504 1504
1505 1505 @classmethod
1506 1506 def get(cls, user_group_id, cache=False):
1507 1507 if not user_group_id:
1508 1508 return
1509 1509
1510 1510 user_group = cls.query()
1511 1511 if cache:
1512 1512 user_group = user_group.options(
1513 1513 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1514 1514 return user_group.get(user_group_id)
1515 1515
1516 1516 def permissions(self, with_admins=True, with_owner=True,
1517 1517 expand_from_user_groups=False):
1518 1518 """
1519 1519 Permissions for user groups
1520 1520 """
1521 1521 _admin_perm = 'usergroup.admin'
1522 1522
1523 1523 owner_row = []
1524 1524 if with_owner:
1525 1525 usr = AttributeDict(self.user.get_dict())
1526 1526 usr.owner_row = True
1527 1527 usr.permission = _admin_perm
1528 1528 owner_row.append(usr)
1529 1529
1530 1530 super_admin_ids = []
1531 1531 super_admin_rows = []
1532 1532 if with_admins:
1533 1533 for usr in User.get_all_super_admins():
1534 1534 super_admin_ids.append(usr.user_id)
1535 1535 # if this admin is also owner, don't double the record
1536 1536 if usr.user_id == owner_row[0].user_id:
1537 1537 owner_row[0].admin_row = True
1538 1538 else:
1539 1539 usr = AttributeDict(usr.get_dict())
1540 1540 usr.admin_row = True
1541 1541 usr.permission = _admin_perm
1542 1542 super_admin_rows.append(usr)
1543 1543
1544 1544 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1545 1545 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1546 1546 joinedload(UserUserGroupToPerm.user),
1547 1547 joinedload(UserUserGroupToPerm.permission),)
1548 1548
1549 1549 # get owners and admins and permissions. We do a trick of re-writing
1550 1550 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1551 1551 # has a global reference and changing one object propagates to all
1552 1552 # others. This means if admin is also an owner admin_row that change
1553 1553 # would propagate to both objects
1554 1554 perm_rows = []
1555 1555 for _usr in q.all():
1556 1556 usr = AttributeDict(_usr.user.get_dict())
1557 1557 # if this user is also owner/admin, mark as duplicate record
1558 1558 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1559 1559 usr.duplicate_perm = True
1560 1560 usr.permission = _usr.permission.permission_name
1561 1561 perm_rows.append(usr)
1562 1562
1563 1563 # filter the perm rows by 'default' first and then sort them by
1564 1564 # admin,write,read,none permissions sorted again alphabetically in
1565 1565 # each group
1566 1566 perm_rows = sorted(perm_rows, key=display_user_sort)
1567 1567
1568 1568 user_groups_rows = []
1569 1569 if expand_from_user_groups:
1570 1570 for ug in self.permission_user_groups(with_members=True):
1571 1571 for user_data in ug.members:
1572 1572 user_groups_rows.append(user_data)
1573 1573
1574 1574 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1575 1575
1576 1576 def permission_user_groups(self, with_members=False):
1577 1577 q = UserGroupUserGroupToPerm.query()\
1578 1578 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1579 1579 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1580 1580 joinedload(UserGroupUserGroupToPerm.target_user_group),
1581 1581 joinedload(UserGroupUserGroupToPerm.permission),)
1582 1582
1583 1583 perm_rows = []
1584 1584 for _user_group in q.all():
1585 1585 entry = AttributeDict(_user_group.user_group.get_dict())
1586 1586 entry.permission = _user_group.permission.permission_name
1587 1587 if with_members:
1588 1588 entry.members = [x.user.get_dict()
1589 1589 for x in _user_group.user_group.members]
1590 1590 perm_rows.append(entry)
1591 1591
1592 1592 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1593 1593 return perm_rows
1594 1594
1595 1595 def _get_default_perms(self, user_group, suffix=''):
1596 1596 from rhodecode.model.permission import PermissionModel
1597 1597 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1598 1598
1599 1599 def get_default_perms(self, suffix=''):
1600 1600 return self._get_default_perms(self, suffix)
1601 1601
1602 1602 def get_api_data(self, with_group_members=True, include_secrets=False):
1603 1603 """
1604 1604 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1605 1605 basically forwarded.
1606 1606
1607 1607 """
1608 1608 user_group = self
1609 1609 data = {
1610 1610 'users_group_id': user_group.users_group_id,
1611 1611 'group_name': user_group.users_group_name,
1612 1612 'group_description': user_group.user_group_description,
1613 1613 'active': user_group.users_group_active,
1614 1614 'owner': user_group.user.username,
1615 1615 'sync': user_group.sync,
1616 1616 'owner_email': user_group.user.email,
1617 1617 }
1618 1618
1619 1619 if with_group_members:
1620 1620 users = []
1621 1621 for user in user_group.members:
1622 1622 user = user.user
1623 1623 users.append(user.get_api_data(include_secrets=include_secrets))
1624 1624 data['users'] = users
1625 1625
1626 1626 return data
1627 1627
1628 1628
1629 1629 class UserGroupMember(Base, BaseModel):
1630 1630 __tablename__ = 'users_groups_members'
1631 1631 __table_args__ = (
1632 1632 base_table_args,
1633 1633 )
1634 1634
1635 1635 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1636 1636 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1637 1637 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1638 1638
1639 1639 user = relationship('User', lazy='joined')
1640 1640 users_group = relationship('UserGroup')
1641 1641
1642 1642 def __init__(self, gr_id='', u_id=''):
1643 1643 self.users_group_id = gr_id
1644 1644 self.user_id = u_id
1645 1645
1646 1646
1647 1647 class RepositoryField(Base, BaseModel):
1648 1648 __tablename__ = 'repositories_fields'
1649 1649 __table_args__ = (
1650 1650 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1651 1651 base_table_args,
1652 1652 )
1653 1653
1654 1654 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1655 1655
1656 1656 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1657 1657 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1658 1658 field_key = Column("field_key", String(250))
1659 1659 field_label = Column("field_label", String(1024), nullable=False)
1660 1660 field_value = Column("field_value", String(10000), nullable=False)
1661 1661 field_desc = Column("field_desc", String(1024), nullable=False)
1662 1662 field_type = Column("field_type", String(255), nullable=False, unique=None)
1663 1663 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1664 1664
1665 1665 repository = relationship('Repository')
1666 1666
1667 1667 @property
1668 1668 def field_key_prefixed(self):
1669 1669 return 'ex_%s' % self.field_key
1670 1670
1671 1671 @classmethod
1672 1672 def un_prefix_key(cls, key):
1673 1673 if key.startswith(cls.PREFIX):
1674 1674 return key[len(cls.PREFIX):]
1675 1675 return key
1676 1676
1677 1677 @classmethod
1678 1678 def get_by_key_name(cls, key, repo):
1679 1679 row = cls.query()\
1680 1680 .filter(cls.repository == repo)\
1681 1681 .filter(cls.field_key == key).scalar()
1682 1682 return row
1683 1683
1684 1684
1685 1685 class Repository(Base, BaseModel):
1686 1686 __tablename__ = 'repositories'
1687 1687 __table_args__ = (
1688 1688 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1689 1689 base_table_args,
1690 1690 )
1691 1691 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1692 1692 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1693 1693 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1694 1694
1695 1695 STATE_CREATED = 'repo_state_created'
1696 1696 STATE_PENDING = 'repo_state_pending'
1697 1697 STATE_ERROR = 'repo_state_error'
1698 1698
1699 1699 LOCK_AUTOMATIC = 'lock_auto'
1700 1700 LOCK_API = 'lock_api'
1701 1701 LOCK_WEB = 'lock_web'
1702 1702 LOCK_PULL = 'lock_pull'
1703 1703
1704 1704 NAME_SEP = URL_SEP
1705 1705
1706 1706 repo_id = Column(
1707 1707 "repo_id", Integer(), nullable=False, unique=True, default=None,
1708 1708 primary_key=True)
1709 1709 _repo_name = Column(
1710 1710 "repo_name", Text(), nullable=False, default=None)
1711 1711 repo_name_hash = Column(
1712 1712 "repo_name_hash", String(255), nullable=False, unique=True)
1713 1713 repo_state = Column("repo_state", String(255), nullable=True)
1714 1714
1715 1715 clone_uri = Column(
1716 1716 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1717 1717 default=None)
1718 1718 push_uri = Column(
1719 1719 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1720 1720 default=None)
1721 1721 repo_type = Column(
1722 1722 "repo_type", String(255), nullable=False, unique=False, default=None)
1723 1723 user_id = Column(
1724 1724 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1725 1725 unique=False, default=None)
1726 1726 private = Column(
1727 1727 "private", Boolean(), nullable=True, unique=None, default=None)
1728 1728 archived = Column(
1729 1729 "archived", Boolean(), nullable=True, unique=None, default=None)
1730 1730 enable_statistics = Column(
1731 1731 "statistics", Boolean(), nullable=True, unique=None, default=True)
1732 1732 enable_downloads = Column(
1733 1733 "downloads", Boolean(), nullable=True, unique=None, default=True)
1734 1734 description = Column(
1735 1735 "description", String(10000), nullable=True, unique=None, default=None)
1736 1736 created_on = Column(
1737 1737 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1738 1738 default=datetime.datetime.now)
1739 1739 updated_on = Column(
1740 1740 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1741 1741 default=datetime.datetime.now)
1742 1742 _landing_revision = Column(
1743 1743 "landing_revision", String(255), nullable=False, unique=False,
1744 1744 default=None)
1745 1745 enable_locking = Column(
1746 1746 "enable_locking", Boolean(), nullable=False, unique=None,
1747 1747 default=False)
1748 1748 _locked = Column(
1749 1749 "locked", String(255), nullable=True, unique=False, default=None)
1750 1750 _changeset_cache = Column(
1751 1751 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1752 1752
1753 1753 fork_id = Column(
1754 1754 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1755 1755 nullable=True, unique=False, default=None)
1756 1756 group_id = Column(
1757 1757 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1758 1758 unique=False, default=None)
1759 1759
1760 1760 user = relationship('User', lazy='joined')
1761 1761 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1762 1762 group = relationship('RepoGroup', lazy='joined')
1763 1763 repo_to_perm = relationship(
1764 1764 'UserRepoToPerm', cascade='all',
1765 1765 order_by='UserRepoToPerm.repo_to_perm_id')
1766 1766 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1767 1767 stats = relationship('Statistics', cascade='all', uselist=False)
1768 1768
1769 1769 followers = relationship(
1770 1770 'UserFollowing',
1771 1771 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1772 1772 cascade='all')
1773 1773 extra_fields = relationship(
1774 1774 'RepositoryField', cascade="all, delete-orphan")
1775 1775 logs = relationship('UserLog')
1776 1776 comments = relationship(
1777 1777 'ChangesetComment', cascade="all, delete-orphan")
1778 1778 pull_requests_source = relationship(
1779 1779 'PullRequest',
1780 1780 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1781 1781 cascade="all, delete-orphan")
1782 1782 pull_requests_target = relationship(
1783 1783 'PullRequest',
1784 1784 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1785 1785 cascade="all, delete-orphan")
1786 1786 ui = relationship('RepoRhodeCodeUi', cascade="all")
1787 1787 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1788 1788 integrations = relationship('Integration', cascade="all, delete-orphan")
1789 1789
1790 1790 scoped_tokens = relationship('UserApiKeys', cascade="all")
1791 1791
1792 1792 # no cascade, set NULL
1793 1793 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1794 1794
1795 1795 def __unicode__(self):
1796 1796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1797 1797 safe_unicode(self.repo_name))
1798 1798
1799 1799 @hybrid_property
1800 1800 def description_safe(self):
1801 1801 from rhodecode.lib import helpers as h
1802 1802 return h.escape(self.description)
1803 1803
1804 1804 @hybrid_property
1805 1805 def landing_rev(self):
1806 1806 # always should return [rev_type, rev], e.g ['branch', 'master']
1807 1807 if self._landing_revision:
1808 1808 _rev_info = self._landing_revision.split(':')
1809 1809 if len(_rev_info) < 2:
1810 1810 _rev_info.insert(0, 'rev')
1811 1811 return [_rev_info[0], _rev_info[1]]
1812 1812 return [None, None]
1813 1813
1814 1814 @property
1815 1815 def landing_ref_type(self):
1816 1816 return self.landing_rev[0]
1817 1817
1818 1818 @property
1819 1819 def landing_ref_name(self):
1820 1820 return self.landing_rev[1]
1821 1821
1822 1822 @landing_rev.setter
1823 1823 def landing_rev(self, val):
1824 1824 if ':' not in val:
1825 1825 raise ValueError('value must be delimited with `:` and consist '
1826 1826 'of <rev_type>:<rev>, got %s instead' % val)
1827 1827 self._landing_revision = val
1828 1828
1829 1829 @hybrid_property
1830 1830 def locked(self):
1831 1831 if self._locked:
1832 1832 user_id, timelocked, reason = self._locked.split(':')
1833 1833 lock_values = int(user_id), timelocked, reason
1834 1834 else:
1835 1835 lock_values = [None, None, None]
1836 1836 return lock_values
1837 1837
1838 1838 @locked.setter
1839 1839 def locked(self, val):
1840 1840 if val and isinstance(val, (list, tuple)):
1841 1841 self._locked = ':'.join(map(str, val))
1842 1842 else:
1843 1843 self._locked = None
1844 1844
1845 1845 @classmethod
1846 1846 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1847 1847 from rhodecode.lib.vcs.backends.base import EmptyCommit
1848 1848 dummy = EmptyCommit().__json__()
1849 1849 if not changeset_cache_raw:
1850 1850 dummy['source_repo_id'] = repo_id
1851 1851 return json.loads(json.dumps(dummy))
1852 1852
1853 1853 try:
1854 1854 return json.loads(changeset_cache_raw)
1855 1855 except TypeError:
1856 1856 return dummy
1857 1857 except Exception:
1858 1858 log.error(traceback.format_exc())
1859 1859 return dummy
1860 1860
1861 1861 @hybrid_property
1862 1862 def changeset_cache(self):
1863 1863 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1864 1864
1865 1865 @changeset_cache.setter
1866 1866 def changeset_cache(self, val):
1867 1867 try:
1868 1868 self._changeset_cache = json.dumps(val)
1869 1869 except Exception:
1870 1870 log.error(traceback.format_exc())
1871 1871
1872 1872 @hybrid_property
1873 1873 def repo_name(self):
1874 1874 return self._repo_name
1875 1875
1876 1876 @repo_name.setter
1877 1877 def repo_name(self, value):
1878 1878 self._repo_name = value
1879 1879 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1880 1880
1881 1881 @classmethod
1882 1882 def normalize_repo_name(cls, repo_name):
1883 1883 """
1884 1884 Normalizes os specific repo_name to the format internally stored inside
1885 1885 database using URL_SEP
1886 1886
1887 1887 :param cls:
1888 1888 :param repo_name:
1889 1889 """
1890 1890 return cls.NAME_SEP.join(repo_name.split(os.sep))
1891 1891
1892 1892 @classmethod
1893 1893 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1894 1894 session = Session()
1895 1895 q = session.query(cls).filter(cls.repo_name == repo_name)
1896 1896
1897 1897 if cache:
1898 1898 if identity_cache:
1899 1899 val = cls.identity_cache(session, 'repo_name', repo_name)
1900 1900 if val:
1901 1901 return val
1902 1902 else:
1903 1903 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1904 1904 q = q.options(
1905 1905 FromCache("sql_cache_short", cache_key))
1906 1906
1907 1907 return q.scalar()
1908 1908
1909 1909 @classmethod
1910 1910 def get_by_id_or_repo_name(cls, repoid):
1911 1911 if isinstance(repoid, (int, long)):
1912 1912 try:
1913 1913 repo = cls.get(repoid)
1914 1914 except ValueError:
1915 1915 repo = None
1916 1916 else:
1917 1917 repo = cls.get_by_repo_name(repoid)
1918 1918 return repo
1919 1919
1920 1920 @classmethod
1921 1921 def get_by_full_path(cls, repo_full_path):
1922 1922 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1923 1923 repo_name = cls.normalize_repo_name(repo_name)
1924 1924 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1925 1925
1926 1926 @classmethod
1927 1927 def get_repo_forks(cls, repo_id):
1928 1928 return cls.query().filter(Repository.fork_id == repo_id)
1929 1929
1930 1930 @classmethod
1931 1931 def base_path(cls):
1932 1932 """
1933 1933 Returns base path when all repos are stored
1934 1934
1935 1935 :param cls:
1936 1936 """
1937 1937 q = Session().query(RhodeCodeUi)\
1938 1938 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1939 1939 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1940 1940 return q.one().ui_value
1941 1941
1942 1942 @classmethod
1943 1943 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1944 1944 case_insensitive=True, archived=False):
1945 1945 q = Repository.query()
1946 1946
1947 1947 if not archived:
1948 1948 q = q.filter(Repository.archived.isnot(true()))
1949 1949
1950 1950 if not isinstance(user_id, Optional):
1951 1951 q = q.filter(Repository.user_id == user_id)
1952 1952
1953 1953 if not isinstance(group_id, Optional):
1954 1954 q = q.filter(Repository.group_id == group_id)
1955 1955
1956 1956 if case_insensitive:
1957 1957 q = q.order_by(func.lower(Repository.repo_name))
1958 1958 else:
1959 1959 q = q.order_by(Repository.repo_name)
1960 1960
1961 1961 return q.all()
1962 1962
1963 1963 @property
1964 1964 def repo_uid(self):
1965 1965 return '_{}'.format(self.repo_id)
1966 1966
1967 1967 @property
1968 1968 def forks(self):
1969 1969 """
1970 1970 Return forks of this repo
1971 1971 """
1972 1972 return Repository.get_repo_forks(self.repo_id)
1973 1973
1974 1974 @property
1975 1975 def parent(self):
1976 1976 """
1977 1977 Returns fork parent
1978 1978 """
1979 1979 return self.fork
1980 1980
1981 1981 @property
1982 1982 def just_name(self):
1983 1983 return self.repo_name.split(self.NAME_SEP)[-1]
1984 1984
1985 1985 @property
1986 1986 def groups_with_parents(self):
1987 1987 groups = []
1988 1988 if self.group is None:
1989 1989 return groups
1990 1990
1991 1991 cur_gr = self.group
1992 1992 groups.insert(0, cur_gr)
1993 1993 while 1:
1994 1994 gr = getattr(cur_gr, 'parent_group', None)
1995 1995 cur_gr = cur_gr.parent_group
1996 1996 if gr is None:
1997 1997 break
1998 1998 groups.insert(0, gr)
1999 1999
2000 2000 return groups
2001 2001
2002 2002 @property
2003 2003 def groups_and_repo(self):
2004 2004 return self.groups_with_parents, self
2005 2005
2006 2006 @LazyProperty
2007 2007 def repo_path(self):
2008 2008 """
2009 2009 Returns base full path for that repository means where it actually
2010 2010 exists on a filesystem
2011 2011 """
2012 2012 q = Session().query(RhodeCodeUi).filter(
2013 2013 RhodeCodeUi.ui_key == self.NAME_SEP)
2014 2014 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2015 2015 return q.one().ui_value
2016 2016
2017 2017 @property
2018 2018 def repo_full_path(self):
2019 2019 p = [self.repo_path]
2020 2020 # we need to split the name by / since this is how we store the
2021 2021 # names in the database, but that eventually needs to be converted
2022 2022 # into a valid system path
2023 2023 p += self.repo_name.split(self.NAME_SEP)
2024 2024 return os.path.join(*map(safe_unicode, p))
2025 2025
2026 2026 @property
2027 2027 def cache_keys(self):
2028 2028 """
2029 2029 Returns associated cache keys for that repo
2030 2030 """
2031 2031 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2032 2032 repo_id=self.repo_id)
2033 2033 return CacheKey.query()\
2034 2034 .filter(CacheKey.cache_args == invalidation_namespace)\
2035 2035 .order_by(CacheKey.cache_key)\
2036 2036 .all()
2037 2037
2038 2038 @property
2039 2039 def cached_diffs_relative_dir(self):
2040 2040 """
2041 2041 Return a relative to the repository store path of cached diffs
2042 2042 used for safe display for users, who shouldn't know the absolute store
2043 2043 path
2044 2044 """
2045 2045 return os.path.join(
2046 2046 os.path.dirname(self.repo_name),
2047 2047 self.cached_diffs_dir.split(os.path.sep)[-1])
2048 2048
2049 2049 @property
2050 2050 def cached_diffs_dir(self):
2051 2051 path = self.repo_full_path
2052 2052 return os.path.join(
2053 2053 os.path.dirname(path),
2054 2054 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2055 2055
2056 2056 def cached_diffs(self):
2057 2057 diff_cache_dir = self.cached_diffs_dir
2058 2058 if os.path.isdir(diff_cache_dir):
2059 2059 return os.listdir(diff_cache_dir)
2060 2060 return []
2061 2061
2062 2062 def shadow_repos(self):
2063 2063 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2064 2064 return [
2065 2065 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2066 2066 if x.startswith(shadow_repos_pattern)]
2067 2067
2068 2068 def get_new_name(self, repo_name):
2069 2069 """
2070 2070 returns new full repository name based on assigned group and new new
2071 2071
2072 2072 :param group_name:
2073 2073 """
2074 2074 path_prefix = self.group.full_path_splitted if self.group else []
2075 2075 return self.NAME_SEP.join(path_prefix + [repo_name])
2076 2076
2077 2077 @property
2078 2078 def _config(self):
2079 2079 """
2080 2080 Returns db based config object.
2081 2081 """
2082 2082 from rhodecode.lib.utils import make_db_config
2083 2083 return make_db_config(clear_session=False, repo=self)
2084 2084
2085 2085 def permissions(self, with_admins=True, with_owner=True,
2086 2086 expand_from_user_groups=False):
2087 2087 """
2088 2088 Permissions for repositories
2089 2089 """
2090 2090 _admin_perm = 'repository.admin'
2091 2091
2092 2092 owner_row = []
2093 2093 if with_owner:
2094 2094 usr = AttributeDict(self.user.get_dict())
2095 2095 usr.owner_row = True
2096 2096 usr.permission = _admin_perm
2097 2097 usr.permission_id = None
2098 2098 owner_row.append(usr)
2099 2099
2100 2100 super_admin_ids = []
2101 2101 super_admin_rows = []
2102 2102 if with_admins:
2103 2103 for usr in User.get_all_super_admins():
2104 2104 super_admin_ids.append(usr.user_id)
2105 2105 # if this admin is also owner, don't double the record
2106 2106 if usr.user_id == owner_row[0].user_id:
2107 2107 owner_row[0].admin_row = True
2108 2108 else:
2109 2109 usr = AttributeDict(usr.get_dict())
2110 2110 usr.admin_row = True
2111 2111 usr.permission = _admin_perm
2112 2112 usr.permission_id = None
2113 2113 super_admin_rows.append(usr)
2114 2114
2115 2115 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2116 2116 q = q.options(joinedload(UserRepoToPerm.repository),
2117 2117 joinedload(UserRepoToPerm.user),
2118 2118 joinedload(UserRepoToPerm.permission),)
2119 2119
2120 2120 # get owners and admins and permissions. We do a trick of re-writing
2121 2121 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2122 2122 # has a global reference and changing one object propagates to all
2123 2123 # others. This means if admin is also an owner admin_row that change
2124 2124 # would propagate to both objects
2125 2125 perm_rows = []
2126 2126 for _usr in q.all():
2127 2127 usr = AttributeDict(_usr.user.get_dict())
2128 2128 # if this user is also owner/admin, mark as duplicate record
2129 2129 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2130 2130 usr.duplicate_perm = True
2131 2131 # also check if this permission is maybe used by branch_permissions
2132 2132 if _usr.branch_perm_entry:
2133 2133 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2134 2134
2135 2135 usr.permission = _usr.permission.permission_name
2136 2136 usr.permission_id = _usr.repo_to_perm_id
2137 2137 perm_rows.append(usr)
2138 2138
2139 2139 # filter the perm rows by 'default' first and then sort them by
2140 2140 # admin,write,read,none permissions sorted again alphabetically in
2141 2141 # each group
2142 2142 perm_rows = sorted(perm_rows, key=display_user_sort)
2143 2143
2144 2144 user_groups_rows = []
2145 2145 if expand_from_user_groups:
2146 2146 for ug in self.permission_user_groups(with_members=True):
2147 2147 for user_data in ug.members:
2148 2148 user_groups_rows.append(user_data)
2149 2149
2150 2150 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2151 2151
2152 2152 def permission_user_groups(self, with_members=True):
2153 2153 q = UserGroupRepoToPerm.query()\
2154 2154 .filter(UserGroupRepoToPerm.repository == self)
2155 2155 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2156 2156 joinedload(UserGroupRepoToPerm.users_group),
2157 2157 joinedload(UserGroupRepoToPerm.permission),)
2158 2158
2159 2159 perm_rows = []
2160 2160 for _user_group in q.all():
2161 2161 entry = AttributeDict(_user_group.users_group.get_dict())
2162 2162 entry.permission = _user_group.permission.permission_name
2163 2163 if with_members:
2164 2164 entry.members = [x.user.get_dict()
2165 2165 for x in _user_group.users_group.members]
2166 2166 perm_rows.append(entry)
2167 2167
2168 2168 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2169 2169 return perm_rows
2170 2170
2171 2171 def get_api_data(self, include_secrets=False):
2172 2172 """
2173 2173 Common function for generating repo api data
2174 2174
2175 2175 :param include_secrets: See :meth:`User.get_api_data`.
2176 2176
2177 2177 """
2178 2178 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2179 2179 # move this methods on models level.
2180 2180 from rhodecode.model.settings import SettingsModel
2181 2181 from rhodecode.model.repo import RepoModel
2182 2182
2183 2183 repo = self
2184 2184 _user_id, _time, _reason = self.locked
2185 2185
2186 2186 data = {
2187 2187 'repo_id': repo.repo_id,
2188 2188 'repo_name': repo.repo_name,
2189 2189 'repo_type': repo.repo_type,
2190 2190 'clone_uri': repo.clone_uri or '',
2191 2191 'push_uri': repo.push_uri or '',
2192 2192 'url': RepoModel().get_url(self),
2193 2193 'private': repo.private,
2194 2194 'created_on': repo.created_on,
2195 2195 'description': repo.description_safe,
2196 2196 'landing_rev': repo.landing_rev,
2197 2197 'owner': repo.user.username,
2198 2198 'fork_of': repo.fork.repo_name if repo.fork else None,
2199 2199 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2200 2200 'enable_statistics': repo.enable_statistics,
2201 2201 'enable_locking': repo.enable_locking,
2202 2202 'enable_downloads': repo.enable_downloads,
2203 2203 'last_changeset': repo.changeset_cache,
2204 2204 'locked_by': User.get(_user_id).get_api_data(
2205 2205 include_secrets=include_secrets) if _user_id else None,
2206 2206 'locked_date': time_to_datetime(_time) if _time else None,
2207 2207 'lock_reason': _reason if _reason else None,
2208 2208 }
2209 2209
2210 2210 # TODO: mikhail: should be per-repo settings here
2211 2211 rc_config = SettingsModel().get_all_settings()
2212 2212 repository_fields = str2bool(
2213 2213 rc_config.get('rhodecode_repository_fields'))
2214 2214 if repository_fields:
2215 2215 for f in self.extra_fields:
2216 2216 data[f.field_key_prefixed] = f.field_value
2217 2217
2218 2218 return data
2219 2219
2220 2220 @classmethod
2221 2221 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2222 2222 if not lock_time:
2223 2223 lock_time = time.time()
2224 2224 if not lock_reason:
2225 2225 lock_reason = cls.LOCK_AUTOMATIC
2226 2226 repo.locked = [user_id, lock_time, lock_reason]
2227 2227 Session().add(repo)
2228 2228 Session().commit()
2229 2229
2230 2230 @classmethod
2231 2231 def unlock(cls, repo):
2232 2232 repo.locked = None
2233 2233 Session().add(repo)
2234 2234 Session().commit()
2235 2235
2236 2236 @classmethod
2237 2237 def getlock(cls, repo):
2238 2238 return repo.locked
2239 2239
2240 2240 def is_user_lock(self, user_id):
2241 2241 if self.lock[0]:
2242 2242 lock_user_id = safe_int(self.lock[0])
2243 2243 user_id = safe_int(user_id)
2244 2244 # both are ints, and they are equal
2245 2245 return all([lock_user_id, user_id]) and lock_user_id == user_id
2246 2246
2247 2247 return False
2248 2248
2249 2249 def get_locking_state(self, action, user_id, only_when_enabled=True):
2250 2250 """
2251 2251 Checks locking on this repository, if locking is enabled and lock is
2252 2252 present returns a tuple of make_lock, locked, locked_by.
2253 2253 make_lock can have 3 states None (do nothing) True, make lock
2254 2254 False release lock, This value is later propagated to hooks, which
2255 2255 do the locking. Think about this as signals passed to hooks what to do.
2256 2256
2257 2257 """
2258 2258 # TODO: johbo: This is part of the business logic and should be moved
2259 2259 # into the RepositoryModel.
2260 2260
2261 2261 if action not in ('push', 'pull'):
2262 2262 raise ValueError("Invalid action value: %s" % repr(action))
2263 2263
2264 2264 # defines if locked error should be thrown to user
2265 2265 currently_locked = False
2266 2266 # defines if new lock should be made, tri-state
2267 2267 make_lock = None
2268 2268 repo = self
2269 2269 user = User.get(user_id)
2270 2270
2271 2271 lock_info = repo.locked
2272 2272
2273 2273 if repo and (repo.enable_locking or not only_when_enabled):
2274 2274 if action == 'push':
2275 2275 # check if it's already locked !, if it is compare users
2276 2276 locked_by_user_id = lock_info[0]
2277 2277 if user.user_id == locked_by_user_id:
2278 2278 log.debug(
2279 2279 'Got `push` action from user %s, now unlocking', user)
2280 2280 # unlock if we have push from user who locked
2281 2281 make_lock = False
2282 2282 else:
2283 2283 # we're not the same user who locked, ban with
2284 2284 # code defined in settings (default is 423 HTTP Locked) !
2285 2285 log.debug('Repo %s is currently locked by %s', repo, user)
2286 2286 currently_locked = True
2287 2287 elif action == 'pull':
2288 2288 # [0] user [1] date
2289 2289 if lock_info[0] and lock_info[1]:
2290 2290 log.debug('Repo %s is currently locked by %s', repo, user)
2291 2291 currently_locked = True
2292 2292 else:
2293 2293 log.debug('Setting lock on repo %s by %s', repo, user)
2294 2294 make_lock = True
2295 2295
2296 2296 else:
2297 2297 log.debug('Repository %s do not have locking enabled', repo)
2298 2298
2299 2299 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2300 2300 make_lock, currently_locked, lock_info)
2301 2301
2302 2302 from rhodecode.lib.auth import HasRepoPermissionAny
2303 2303 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2304 2304 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2305 2305 # if we don't have at least write permission we cannot make a lock
2306 2306 log.debug('lock state reset back to FALSE due to lack '
2307 2307 'of at least read permission')
2308 2308 make_lock = False
2309 2309
2310 2310 return make_lock, currently_locked, lock_info
2311 2311
2312 2312 @property
2313 2313 def last_commit_cache_update_diff(self):
2314 2314 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2315 2315
2316 2316 @classmethod
2317 2317 def _load_commit_change(cls, last_commit_cache):
2318 2318 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2319 2319 empty_date = datetime.datetime.fromtimestamp(0)
2320 2320 date_latest = last_commit_cache.get('date', empty_date)
2321 2321 try:
2322 2322 return parse_datetime(date_latest)
2323 2323 except Exception:
2324 2324 return empty_date
2325 2325
2326 2326 @property
2327 2327 def last_commit_change(self):
2328 2328 return self._load_commit_change(self.changeset_cache)
2329 2329
2330 2330 @property
2331 2331 def last_db_change(self):
2332 2332 return self.updated_on
2333 2333
2334 2334 @property
2335 2335 def clone_uri_hidden(self):
2336 2336 clone_uri = self.clone_uri
2337 2337 if clone_uri:
2338 2338 import urlobject
2339 2339 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2340 2340 if url_obj.password:
2341 2341 clone_uri = url_obj.with_password('*****')
2342 2342 return clone_uri
2343 2343
2344 2344 @property
2345 2345 def push_uri_hidden(self):
2346 2346 push_uri = self.push_uri
2347 2347 if push_uri:
2348 2348 import urlobject
2349 2349 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2350 2350 if url_obj.password:
2351 2351 push_uri = url_obj.with_password('*****')
2352 2352 return push_uri
2353 2353
2354 2354 def clone_url(self, **override):
2355 2355 from rhodecode.model.settings import SettingsModel
2356 2356
2357 2357 uri_tmpl = None
2358 2358 if 'with_id' in override:
2359 2359 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2360 2360 del override['with_id']
2361 2361
2362 2362 if 'uri_tmpl' in override:
2363 2363 uri_tmpl = override['uri_tmpl']
2364 2364 del override['uri_tmpl']
2365 2365
2366 2366 ssh = False
2367 2367 if 'ssh' in override:
2368 2368 ssh = True
2369 2369 del override['ssh']
2370 2370
2371 2371 # we didn't override our tmpl from **overrides
2372 2372 request = get_current_request()
2373 2373 if not uri_tmpl:
2374 2374 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2375 2375 rc_config = request.call_context.rc_config
2376 2376 else:
2377 2377 rc_config = SettingsModel().get_all_settings(cache=True)
2378 2378
2379 2379 if ssh:
2380 2380 uri_tmpl = rc_config.get(
2381 2381 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2382 2382
2383 2383 else:
2384 2384 uri_tmpl = rc_config.get(
2385 2385 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2386 2386
2387 2387 return get_clone_url(request=request,
2388 2388 uri_tmpl=uri_tmpl,
2389 2389 repo_name=self.repo_name,
2390 2390 repo_id=self.repo_id,
2391 2391 repo_type=self.repo_type,
2392 2392 **override)
2393 2393
2394 2394 def set_state(self, state):
2395 2395 self.repo_state = state
2396 2396 Session().add(self)
2397 2397 #==========================================================================
2398 2398 # SCM PROPERTIES
2399 2399 #==========================================================================
2400 2400
2401 2401 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2402 2402 return get_commit_safe(
2403 2403 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2404 2404 maybe_unreachable=maybe_unreachable)
2405 2405
2406 2406 def get_changeset(self, rev=None, pre_load=None):
2407 2407 warnings.warn("Use get_commit", DeprecationWarning)
2408 2408 commit_id = None
2409 2409 commit_idx = None
2410 2410 if isinstance(rev, compat.string_types):
2411 2411 commit_id = rev
2412 2412 else:
2413 2413 commit_idx = rev
2414 2414 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2415 2415 pre_load=pre_load)
2416 2416
2417 2417 def get_landing_commit(self):
2418 2418 """
2419 2419 Returns landing commit, or if that doesn't exist returns the tip
2420 2420 """
2421 2421 _rev_type, _rev = self.landing_rev
2422 2422 commit = self.get_commit(_rev)
2423 2423 if isinstance(commit, EmptyCommit):
2424 2424 return self.get_commit()
2425 2425 return commit
2426 2426
2427 2427 def flush_commit_cache(self):
2428 2428 self.update_commit_cache(cs_cache={'raw_id':'0'})
2429 2429 self.update_commit_cache()
2430 2430
2431 2431 def update_commit_cache(self, cs_cache=None, config=None):
2432 2432 """
2433 2433 Update cache of last commit for repository
2434 2434 cache_keys should be::
2435 2435
2436 2436 source_repo_id
2437 2437 short_id
2438 2438 raw_id
2439 2439 revision
2440 2440 parents
2441 2441 message
2442 2442 date
2443 2443 author
2444 2444 updated_on
2445 2445
2446 2446 """
2447 2447 from rhodecode.lib.vcs.backends.base import BaseChangeset
2448 2448 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2449 2449 empty_date = datetime.datetime.fromtimestamp(0)
2450 2450
2451 2451 if cs_cache is None:
2452 2452 # use no-cache version here
2453 2453 try:
2454 2454 scm_repo = self.scm_instance(cache=False, config=config)
2455 2455 except VCSError:
2456 2456 scm_repo = None
2457 2457 empty = scm_repo is None or scm_repo.is_empty()
2458 2458
2459 2459 if not empty:
2460 2460 cs_cache = scm_repo.get_commit(
2461 2461 pre_load=["author", "date", "message", "parents", "branch"])
2462 2462 else:
2463 2463 cs_cache = EmptyCommit()
2464 2464
2465 2465 if isinstance(cs_cache, BaseChangeset):
2466 2466 cs_cache = cs_cache.__json__()
2467 2467
2468 2468 def is_outdated(new_cs_cache):
2469 2469 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2470 2470 new_cs_cache['revision'] != self.changeset_cache['revision']):
2471 2471 return True
2472 2472 return False
2473 2473
2474 2474 # check if we have maybe already latest cached revision
2475 2475 if is_outdated(cs_cache) or not self.changeset_cache:
2476 2476 _current_datetime = datetime.datetime.utcnow()
2477 2477 last_change = cs_cache.get('date') or _current_datetime
2478 2478 # we check if last update is newer than the new value
2479 2479 # if yes, we use the current timestamp instead. Imagine you get
2480 2480 # old commit pushed 1y ago, we'd set last update 1y to ago.
2481 2481 last_change_timestamp = datetime_to_time(last_change)
2482 2482 current_timestamp = datetime_to_time(last_change)
2483 2483 if last_change_timestamp > current_timestamp and not empty:
2484 2484 cs_cache['date'] = _current_datetime
2485 2485
2486 2486 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2487 2487 cs_cache['updated_on'] = time.time()
2488 2488 self.changeset_cache = cs_cache
2489 2489 self.updated_on = last_change
2490 2490 Session().add(self)
2491 2491 Session().commit()
2492 2492
2493 2493 else:
2494 2494 if empty:
2495 2495 cs_cache = EmptyCommit().__json__()
2496 2496 else:
2497 2497 cs_cache = self.changeset_cache
2498 2498
2499 2499 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2500 2500
2501 2501 cs_cache['updated_on'] = time.time()
2502 2502 self.changeset_cache = cs_cache
2503 2503 self.updated_on = _date_latest
2504 2504 Session().add(self)
2505 2505 Session().commit()
2506 2506
2507 2507 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2508 2508 self.repo_name, cs_cache, _date_latest)
2509 2509
2510 2510 @property
2511 2511 def tip(self):
2512 2512 return self.get_commit('tip')
2513 2513
2514 2514 @property
2515 2515 def author(self):
2516 2516 return self.tip.author
2517 2517
2518 2518 @property
2519 2519 def last_change(self):
2520 2520 return self.scm_instance().last_change
2521 2521
2522 2522 def get_comments(self, revisions=None):
2523 2523 """
2524 2524 Returns comments for this repository grouped by revisions
2525 2525
2526 2526 :param revisions: filter query by revisions only
2527 2527 """
2528 2528 cmts = ChangesetComment.query()\
2529 2529 .filter(ChangesetComment.repo == self)
2530 2530 if revisions:
2531 2531 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2532 2532 grouped = collections.defaultdict(list)
2533 2533 for cmt in cmts.all():
2534 2534 grouped[cmt.revision].append(cmt)
2535 2535 return grouped
2536 2536
2537 2537 def statuses(self, revisions=None):
2538 2538 """
2539 2539 Returns statuses for this repository
2540 2540
2541 2541 :param revisions: list of revisions to get statuses for
2542 2542 """
2543 2543 statuses = ChangesetStatus.query()\
2544 2544 .filter(ChangesetStatus.repo == self)\
2545 2545 .filter(ChangesetStatus.version == 0)
2546 2546
2547 2547 if revisions:
2548 2548 # Try doing the filtering in chunks to avoid hitting limits
2549 2549 size = 500
2550 2550 status_results = []
2551 2551 for chunk in xrange(0, len(revisions), size):
2552 2552 status_results += statuses.filter(
2553 2553 ChangesetStatus.revision.in_(
2554 2554 revisions[chunk: chunk+size])
2555 2555 ).all()
2556 2556 else:
2557 2557 status_results = statuses.all()
2558 2558
2559 2559 grouped = {}
2560 2560
2561 2561 # maybe we have open new pullrequest without a status?
2562 2562 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2563 2563 status_lbl = ChangesetStatus.get_status_lbl(stat)
2564 2564 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2565 2565 for rev in pr.revisions:
2566 2566 pr_id = pr.pull_request_id
2567 2567 pr_repo = pr.target_repo.repo_name
2568 2568 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2569 2569
2570 2570 for stat in status_results:
2571 2571 pr_id = pr_repo = None
2572 2572 if stat.pull_request:
2573 2573 pr_id = stat.pull_request.pull_request_id
2574 2574 pr_repo = stat.pull_request.target_repo.repo_name
2575 2575 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2576 2576 pr_id, pr_repo]
2577 2577 return grouped
2578 2578
2579 2579 # ==========================================================================
2580 2580 # SCM CACHE INSTANCE
2581 2581 # ==========================================================================
2582 2582
2583 2583 def scm_instance(self, **kwargs):
2584 2584 import rhodecode
2585 2585
2586 2586 # Passing a config will not hit the cache currently only used
2587 2587 # for repo2dbmapper
2588 2588 config = kwargs.pop('config', None)
2589 2589 cache = kwargs.pop('cache', None)
2590 2590 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2591 2591 if vcs_full_cache is not None:
2592 2592 # allows override global config
2593 2593 full_cache = vcs_full_cache
2594 2594 else:
2595 2595 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2596 2596 # if cache is NOT defined use default global, else we have a full
2597 2597 # control over cache behaviour
2598 2598 if cache is None and full_cache and not config:
2599 2599 log.debug('Initializing pure cached instance for %s', self.repo_path)
2600 2600 return self._get_instance_cached()
2601 2601
2602 2602 # cache here is sent to the "vcs server"
2603 2603 return self._get_instance(cache=bool(cache), config=config)
2604 2604
2605 2605 def _get_instance_cached(self):
2606 2606 from rhodecode.lib import rc_cache
2607 2607
2608 2608 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2609 2609 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2610 2610 repo_id=self.repo_id)
2611 2611 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2612 2612
2613 2613 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2614 2614 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2615 2615 return self._get_instance(repo_state_uid=_cache_state_uid)
2616 2616
2617 2617 # we must use thread scoped cache here,
2618 2618 # because each thread of gevent needs it's own not shared connection and cache
2619 2619 # we also alter `args` so the cache key is individual for every green thread.
2620 2620 inv_context_manager = rc_cache.InvalidationContext(
2621 2621 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2622 2622 thread_scoped=True)
2623 2623 with inv_context_manager as invalidation_context:
2624 2624 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2625 2625 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2626 2626
2627 2627 # re-compute and store cache if we get invalidate signal
2628 2628 if invalidation_context.should_invalidate():
2629 2629 instance = get_instance_cached.refresh(*args)
2630 2630 else:
2631 2631 instance = get_instance_cached(*args)
2632 2632
2633 2633 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2634 2634 return instance
2635 2635
2636 2636 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2637 2637 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2638 2638 self.repo_type, self.repo_path, cache)
2639 2639 config = config or self._config
2640 2640 custom_wire = {
2641 2641 'cache': cache, # controls the vcs.remote cache
2642 2642 'repo_state_uid': repo_state_uid
2643 2643 }
2644 2644 repo = get_vcs_instance(
2645 2645 repo_path=safe_str(self.repo_full_path),
2646 2646 config=config,
2647 2647 with_wire=custom_wire,
2648 2648 create=False,
2649 2649 _vcs_alias=self.repo_type)
2650 2650 if repo is not None:
2651 2651 repo.count() # cache rebuild
2652 2652 return repo
2653 2653
2654 2654 def get_shadow_repository_path(self, workspace_id):
2655 2655 from rhodecode.lib.vcs.backends.base import BaseRepository
2656 2656 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2657 2657 self.repo_full_path, self.repo_id, workspace_id)
2658 2658 return shadow_repo_path
2659 2659
2660 2660 def __json__(self):
2661 2661 return {'landing_rev': self.landing_rev}
2662 2662
2663 2663 def get_dict(self):
2664 2664
2665 2665 # Since we transformed `repo_name` to a hybrid property, we need to
2666 2666 # keep compatibility with the code which uses `repo_name` field.
2667 2667
2668 2668 result = super(Repository, self).get_dict()
2669 2669 result['repo_name'] = result.pop('_repo_name', None)
2670 2670 return result
2671 2671
2672 2672
2673 2673 class RepoGroup(Base, BaseModel):
2674 2674 __tablename__ = 'groups'
2675 2675 __table_args__ = (
2676 2676 UniqueConstraint('group_name', 'group_parent_id'),
2677 2677 base_table_args,
2678 2678 )
2679 2679 __mapper_args__ = {'order_by': 'group_name'}
2680 2680
2681 2681 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2682 2682
2683 2683 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2684 2684 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2685 2685 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2686 2686 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2687 2687 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2688 2688 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2689 2689 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2690 2690 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2691 2691 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2692 2692 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2693 2693 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2694 2694
2695 2695 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2696 2696 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2697 2697 parent_group = relationship('RepoGroup', remote_side=group_id)
2698 2698 user = relationship('User')
2699 2699 integrations = relationship('Integration', cascade="all, delete-orphan")
2700 2700
2701 2701 # no cascade, set NULL
2702 2702 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2703 2703
2704 2704 def __init__(self, group_name='', parent_group=None):
2705 2705 self.group_name = group_name
2706 2706 self.parent_group = parent_group
2707 2707
2708 2708 def __unicode__(self):
2709 2709 return u"<%s('id:%s:%s')>" % (
2710 2710 self.__class__.__name__, self.group_id, self.group_name)
2711 2711
2712 2712 @hybrid_property
2713 2713 def group_name(self):
2714 2714 return self._group_name
2715 2715
2716 2716 @group_name.setter
2717 2717 def group_name(self, value):
2718 2718 self._group_name = value
2719 2719 self.group_name_hash = self.hash_repo_group_name(value)
2720 2720
2721 2721 @classmethod
2722 2722 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2723 2723 from rhodecode.lib.vcs.backends.base import EmptyCommit
2724 2724 dummy = EmptyCommit().__json__()
2725 2725 if not changeset_cache_raw:
2726 2726 dummy['source_repo_id'] = repo_id
2727 2727 return json.loads(json.dumps(dummy))
2728 2728
2729 2729 try:
2730 2730 return json.loads(changeset_cache_raw)
2731 2731 except TypeError:
2732 2732 return dummy
2733 2733 except Exception:
2734 2734 log.error(traceback.format_exc())
2735 2735 return dummy
2736 2736
2737 2737 @hybrid_property
2738 2738 def changeset_cache(self):
2739 2739 return self._load_changeset_cache('', self._changeset_cache)
2740 2740
2741 2741 @changeset_cache.setter
2742 2742 def changeset_cache(self, val):
2743 2743 try:
2744 2744 self._changeset_cache = json.dumps(val)
2745 2745 except Exception:
2746 2746 log.error(traceback.format_exc())
2747 2747
2748 2748 @validates('group_parent_id')
2749 2749 def validate_group_parent_id(self, key, val):
2750 2750 """
2751 2751 Check cycle references for a parent group to self
2752 2752 """
2753 2753 if self.group_id and val:
2754 2754 assert val != self.group_id
2755 2755
2756 2756 return val
2757 2757
2758 2758 @hybrid_property
2759 2759 def description_safe(self):
2760 2760 from rhodecode.lib import helpers as h
2761 2761 return h.escape(self.group_description)
2762 2762
2763 2763 @classmethod
2764 2764 def hash_repo_group_name(cls, repo_group_name):
2765 2765 val = remove_formatting(repo_group_name)
2766 2766 val = safe_str(val).lower()
2767 2767 chars = []
2768 2768 for c in val:
2769 2769 if c not in string.ascii_letters:
2770 2770 c = str(ord(c))
2771 2771 chars.append(c)
2772 2772
2773 2773 return ''.join(chars)
2774 2774
2775 2775 @classmethod
2776 2776 def _generate_choice(cls, repo_group):
2777 2777 from webhelpers2.html import literal as _literal
2778 2778 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2779 2779 return repo_group.group_id, _name(repo_group.full_path_splitted)
2780 2780
2781 2781 @classmethod
2782 2782 def groups_choices(cls, groups=None, show_empty_group=True):
2783 2783 if not groups:
2784 2784 groups = cls.query().all()
2785 2785
2786 2786 repo_groups = []
2787 2787 if show_empty_group:
2788 2788 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2789 2789
2790 2790 repo_groups.extend([cls._generate_choice(x) for x in groups])
2791 2791
2792 2792 repo_groups = sorted(
2793 2793 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2794 2794 return repo_groups
2795 2795
2796 2796 @classmethod
2797 2797 def url_sep(cls):
2798 2798 return URL_SEP
2799 2799
2800 2800 @classmethod
2801 2801 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2802 2802 if case_insensitive:
2803 2803 gr = cls.query().filter(func.lower(cls.group_name)
2804 2804 == func.lower(group_name))
2805 2805 else:
2806 2806 gr = cls.query().filter(cls.group_name == group_name)
2807 2807 if cache:
2808 2808 name_key = _hash_key(group_name)
2809 2809 gr = gr.options(
2810 2810 FromCache("sql_cache_short", "get_group_%s" % name_key))
2811 2811 return gr.scalar()
2812 2812
2813 2813 @classmethod
2814 2814 def get_user_personal_repo_group(cls, user_id):
2815 2815 user = User.get(user_id)
2816 2816 if user.username == User.DEFAULT_USER:
2817 2817 return None
2818 2818
2819 2819 return cls.query()\
2820 2820 .filter(cls.personal == true()) \
2821 2821 .filter(cls.user == user) \
2822 2822 .order_by(cls.group_id.asc()) \
2823 2823 .first()
2824 2824
2825 2825 @classmethod
2826 2826 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2827 2827 case_insensitive=True):
2828 2828 q = RepoGroup.query()
2829 2829
2830 2830 if not isinstance(user_id, Optional):
2831 2831 q = q.filter(RepoGroup.user_id == user_id)
2832 2832
2833 2833 if not isinstance(group_id, Optional):
2834 2834 q = q.filter(RepoGroup.group_parent_id == group_id)
2835 2835
2836 2836 if case_insensitive:
2837 2837 q = q.order_by(func.lower(RepoGroup.group_name))
2838 2838 else:
2839 2839 q = q.order_by(RepoGroup.group_name)
2840 2840 return q.all()
2841 2841
2842 2842 @property
2843 2843 def parents(self, parents_recursion_limit=10):
2844 2844 groups = []
2845 2845 if self.parent_group is None:
2846 2846 return groups
2847 2847 cur_gr = self.parent_group
2848 2848 groups.insert(0, cur_gr)
2849 2849 cnt = 0
2850 2850 while 1:
2851 2851 cnt += 1
2852 2852 gr = getattr(cur_gr, 'parent_group', None)
2853 2853 cur_gr = cur_gr.parent_group
2854 2854 if gr is None:
2855 2855 break
2856 2856 if cnt == parents_recursion_limit:
2857 2857 # this will prevent accidental infinit loops
2858 2858 log.error('more than %s parents found for group %s, stopping '
2859 2859 'recursive parent fetching', parents_recursion_limit, self)
2860 2860 break
2861 2861
2862 2862 groups.insert(0, gr)
2863 2863 return groups
2864 2864
2865 2865 @property
2866 2866 def last_commit_cache_update_diff(self):
2867 2867 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2868 2868
2869 2869 @classmethod
2870 2870 def _load_commit_change(cls, last_commit_cache):
2871 2871 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2872 2872 empty_date = datetime.datetime.fromtimestamp(0)
2873 2873 date_latest = last_commit_cache.get('date', empty_date)
2874 2874 try:
2875 2875 return parse_datetime(date_latest)
2876 2876 except Exception:
2877 2877 return empty_date
2878 2878
2879 2879 @property
2880 2880 def last_commit_change(self):
2881 2881 return self._load_commit_change(self.changeset_cache)
2882 2882
2883 2883 @property
2884 2884 def last_db_change(self):
2885 2885 return self.updated_on
2886 2886
2887 2887 @property
2888 2888 def children(self):
2889 2889 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2890 2890
2891 2891 @property
2892 2892 def name(self):
2893 2893 return self.group_name.split(RepoGroup.url_sep())[-1]
2894 2894
2895 2895 @property
2896 2896 def full_path(self):
2897 2897 return self.group_name
2898 2898
2899 2899 @property
2900 2900 def full_path_splitted(self):
2901 2901 return self.group_name.split(RepoGroup.url_sep())
2902 2902
2903 2903 @property
2904 2904 def repositories(self):
2905 2905 return Repository.query()\
2906 2906 .filter(Repository.group == self)\
2907 2907 .order_by(Repository.repo_name)
2908 2908
2909 2909 @property
2910 2910 def repositories_recursive_count(self):
2911 2911 cnt = self.repositories.count()
2912 2912
2913 2913 def children_count(group):
2914 2914 cnt = 0
2915 2915 for child in group.children:
2916 2916 cnt += child.repositories.count()
2917 2917 cnt += children_count(child)
2918 2918 return cnt
2919 2919
2920 2920 return cnt + children_count(self)
2921 2921
2922 2922 def _recursive_objects(self, include_repos=True, include_groups=True):
2923 2923 all_ = []
2924 2924
2925 2925 def _get_members(root_gr):
2926 2926 if include_repos:
2927 2927 for r in root_gr.repositories:
2928 2928 all_.append(r)
2929 2929 childs = root_gr.children.all()
2930 2930 if childs:
2931 2931 for gr in childs:
2932 2932 if include_groups:
2933 2933 all_.append(gr)
2934 2934 _get_members(gr)
2935 2935
2936 2936 root_group = []
2937 2937 if include_groups:
2938 2938 root_group = [self]
2939 2939
2940 2940 _get_members(self)
2941 2941 return root_group + all_
2942 2942
2943 2943 def recursive_groups_and_repos(self):
2944 2944 """
2945 2945 Recursive return all groups, with repositories in those groups
2946 2946 """
2947 2947 return self._recursive_objects()
2948 2948
2949 2949 def recursive_groups(self):
2950 2950 """
2951 2951 Returns all children groups for this group including children of children
2952 2952 """
2953 2953 return self._recursive_objects(include_repos=False)
2954 2954
2955 2955 def recursive_repos(self):
2956 2956 """
2957 2957 Returns all children repositories for this group
2958 2958 """
2959 2959 return self._recursive_objects(include_groups=False)
2960 2960
2961 2961 def get_new_name(self, group_name):
2962 2962 """
2963 2963 returns new full group name based on parent and new name
2964 2964
2965 2965 :param group_name:
2966 2966 """
2967 2967 path_prefix = (self.parent_group.full_path_splitted if
2968 2968 self.parent_group else [])
2969 2969 return RepoGroup.url_sep().join(path_prefix + [group_name])
2970 2970
2971 2971 def update_commit_cache(self, config=None):
2972 2972 """
2973 2973 Update cache of last commit for newest repository inside this repository group.
2974 2974 cache_keys should be::
2975 2975
2976 2976 source_repo_id
2977 2977 short_id
2978 2978 raw_id
2979 2979 revision
2980 2980 parents
2981 2981 message
2982 2982 date
2983 2983 author
2984 2984
2985 2985 """
2986 2986 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2987 2987 empty_date = datetime.datetime.fromtimestamp(0)
2988 2988
2989 2989 def repo_groups_and_repos(root_gr):
2990 2990 for _repo in root_gr.repositories:
2991 2991 yield _repo
2992 2992 for child_group in root_gr.children.all():
2993 2993 yield child_group
2994 2994
2995 2995 latest_repo_cs_cache = {}
2996 2996 for obj in repo_groups_and_repos(self):
2997 2997 repo_cs_cache = obj.changeset_cache
2998 2998 date_latest = latest_repo_cs_cache.get('date', empty_date)
2999 2999 date_current = repo_cs_cache.get('date', empty_date)
3000 3000 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3001 3001 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3002 3002 latest_repo_cs_cache = repo_cs_cache
3003 3003 if hasattr(obj, 'repo_id'):
3004 3004 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3005 3005 else:
3006 3006 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3007 3007
3008 3008 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3009 3009
3010 3010 latest_repo_cs_cache['updated_on'] = time.time()
3011 3011 self.changeset_cache = latest_repo_cs_cache
3012 3012 self.updated_on = _date_latest
3013 3013 Session().add(self)
3014 3014 Session().commit()
3015 3015
3016 3016 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3017 3017 self.group_name, latest_repo_cs_cache, _date_latest)
3018 3018
3019 3019 def permissions(self, with_admins=True, with_owner=True,
3020 3020 expand_from_user_groups=False):
3021 3021 """
3022 3022 Permissions for repository groups
3023 3023 """
3024 3024 _admin_perm = 'group.admin'
3025 3025
3026 3026 owner_row = []
3027 3027 if with_owner:
3028 3028 usr = AttributeDict(self.user.get_dict())
3029 3029 usr.owner_row = True
3030 3030 usr.permission = _admin_perm
3031 3031 owner_row.append(usr)
3032 3032
3033 3033 super_admin_ids = []
3034 3034 super_admin_rows = []
3035 3035 if with_admins:
3036 3036 for usr in User.get_all_super_admins():
3037 3037 super_admin_ids.append(usr.user_id)
3038 3038 # if this admin is also owner, don't double the record
3039 3039 if usr.user_id == owner_row[0].user_id:
3040 3040 owner_row[0].admin_row = True
3041 3041 else:
3042 3042 usr = AttributeDict(usr.get_dict())
3043 3043 usr.admin_row = True
3044 3044 usr.permission = _admin_perm
3045 3045 super_admin_rows.append(usr)
3046 3046
3047 3047 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3048 3048 q = q.options(joinedload(UserRepoGroupToPerm.group),
3049 3049 joinedload(UserRepoGroupToPerm.user),
3050 3050 joinedload(UserRepoGroupToPerm.permission),)
3051 3051
3052 3052 # get owners and admins and permissions. We do a trick of re-writing
3053 3053 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3054 3054 # has a global reference and changing one object propagates to all
3055 3055 # others. This means if admin is also an owner admin_row that change
3056 3056 # would propagate to both objects
3057 3057 perm_rows = []
3058 3058 for _usr in q.all():
3059 3059 usr = AttributeDict(_usr.user.get_dict())
3060 3060 # if this user is also owner/admin, mark as duplicate record
3061 3061 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3062 3062 usr.duplicate_perm = True
3063 3063 usr.permission = _usr.permission.permission_name
3064 3064 perm_rows.append(usr)
3065 3065
3066 3066 # filter the perm rows by 'default' first and then sort them by
3067 3067 # admin,write,read,none permissions sorted again alphabetically in
3068 3068 # each group
3069 3069 perm_rows = sorted(perm_rows, key=display_user_sort)
3070 3070
3071 3071 user_groups_rows = []
3072 3072 if expand_from_user_groups:
3073 3073 for ug in self.permission_user_groups(with_members=True):
3074 3074 for user_data in ug.members:
3075 3075 user_groups_rows.append(user_data)
3076 3076
3077 3077 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3078 3078
3079 3079 def permission_user_groups(self, with_members=False):
3080 3080 q = UserGroupRepoGroupToPerm.query()\
3081 3081 .filter(UserGroupRepoGroupToPerm.group == self)
3082 3082 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3083 3083 joinedload(UserGroupRepoGroupToPerm.users_group),
3084 3084 joinedload(UserGroupRepoGroupToPerm.permission),)
3085 3085
3086 3086 perm_rows = []
3087 3087 for _user_group in q.all():
3088 3088 entry = AttributeDict(_user_group.users_group.get_dict())
3089 3089 entry.permission = _user_group.permission.permission_name
3090 3090 if with_members:
3091 3091 entry.members = [x.user.get_dict()
3092 3092 for x in _user_group.users_group.members]
3093 3093 perm_rows.append(entry)
3094 3094
3095 3095 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3096 3096 return perm_rows
3097 3097
3098 3098 def get_api_data(self):
3099 3099 """
3100 3100 Common function for generating api data
3101 3101
3102 3102 """
3103 3103 group = self
3104 3104 data = {
3105 3105 'group_id': group.group_id,
3106 3106 'group_name': group.group_name,
3107 3107 'group_description': group.description_safe,
3108 3108 'parent_group': group.parent_group.group_name if group.parent_group else None,
3109 3109 'repositories': [x.repo_name for x in group.repositories],
3110 3110 'owner': group.user.username,
3111 3111 }
3112 3112 return data
3113 3113
3114 3114 def get_dict(self):
3115 3115 # Since we transformed `group_name` to a hybrid property, we need to
3116 3116 # keep compatibility with the code which uses `group_name` field.
3117 3117 result = super(RepoGroup, self).get_dict()
3118 3118 result['group_name'] = result.pop('_group_name', None)
3119 3119 return result
3120 3120
3121 3121
3122 3122 class Permission(Base, BaseModel):
3123 3123 __tablename__ = 'permissions'
3124 3124 __table_args__ = (
3125 3125 Index('p_perm_name_idx', 'permission_name'),
3126 3126 base_table_args,
3127 3127 )
3128 3128
3129 3129 PERMS = [
3130 3130 ('hg.admin', _('RhodeCode Super Administrator')),
3131 3131
3132 3132 ('repository.none', _('Repository no access')),
3133 3133 ('repository.read', _('Repository read access')),
3134 3134 ('repository.write', _('Repository write access')),
3135 3135 ('repository.admin', _('Repository admin access')),
3136 3136
3137 3137 ('group.none', _('Repository group no access')),
3138 3138 ('group.read', _('Repository group read access')),
3139 3139 ('group.write', _('Repository group write access')),
3140 3140 ('group.admin', _('Repository group admin access')),
3141 3141
3142 3142 ('usergroup.none', _('User group no access')),
3143 3143 ('usergroup.read', _('User group read access')),
3144 3144 ('usergroup.write', _('User group write access')),
3145 3145 ('usergroup.admin', _('User group admin access')),
3146 3146
3147 3147 ('branch.none', _('Branch no permissions')),
3148 3148 ('branch.merge', _('Branch access by web merge')),
3149 3149 ('branch.push', _('Branch access by push')),
3150 3150 ('branch.push_force', _('Branch access by push with force')),
3151 3151
3152 3152 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3153 3153 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3154 3154
3155 3155 ('hg.usergroup.create.false', _('User Group creation disabled')),
3156 3156 ('hg.usergroup.create.true', _('User Group creation enabled')),
3157 3157
3158 3158 ('hg.create.none', _('Repository creation disabled')),
3159 3159 ('hg.create.repository', _('Repository creation enabled')),
3160 3160 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3161 3161 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3162 3162
3163 3163 ('hg.fork.none', _('Repository forking disabled')),
3164 3164 ('hg.fork.repository', _('Repository forking enabled')),
3165 3165
3166 3166 ('hg.register.none', _('Registration disabled')),
3167 3167 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3168 3168 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3169 3169
3170 3170 ('hg.password_reset.enabled', _('Password reset enabled')),
3171 3171 ('hg.password_reset.hidden', _('Password reset hidden')),
3172 3172 ('hg.password_reset.disabled', _('Password reset disabled')),
3173 3173
3174 3174 ('hg.extern_activate.manual', _('Manual activation of external account')),
3175 3175 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3176 3176
3177 3177 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3178 3178 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3179 3179 ]
3180 3180
3181 3181 # definition of system default permissions for DEFAULT user, created on
3182 3182 # system setup
3183 3183 DEFAULT_USER_PERMISSIONS = [
3184 3184 # object perms
3185 3185 'repository.read',
3186 3186 'group.read',
3187 3187 'usergroup.read',
3188 3188 # branch, for backward compat we need same value as before so forced pushed
3189 3189 'branch.push_force',
3190 3190 # global
3191 3191 'hg.create.repository',
3192 3192 'hg.repogroup.create.false',
3193 3193 'hg.usergroup.create.false',
3194 3194 'hg.create.write_on_repogroup.true',
3195 3195 'hg.fork.repository',
3196 3196 'hg.register.manual_activate',
3197 3197 'hg.password_reset.enabled',
3198 3198 'hg.extern_activate.auto',
3199 3199 'hg.inherit_default_perms.true',
3200 3200 ]
3201 3201
3202 3202 # defines which permissions are more important higher the more important
3203 3203 # Weight defines which permissions are more important.
3204 3204 # The higher number the more important.
3205 3205 PERM_WEIGHTS = {
3206 3206 'repository.none': 0,
3207 3207 'repository.read': 1,
3208 3208 'repository.write': 3,
3209 3209 'repository.admin': 4,
3210 3210
3211 3211 'group.none': 0,
3212 3212 'group.read': 1,
3213 3213 'group.write': 3,
3214 3214 'group.admin': 4,
3215 3215
3216 3216 'usergroup.none': 0,
3217 3217 'usergroup.read': 1,
3218 3218 'usergroup.write': 3,
3219 3219 'usergroup.admin': 4,
3220 3220
3221 3221 'branch.none': 0,
3222 3222 'branch.merge': 1,
3223 3223 'branch.push': 3,
3224 3224 'branch.push_force': 4,
3225 3225
3226 3226 'hg.repogroup.create.false': 0,
3227 3227 'hg.repogroup.create.true': 1,
3228 3228
3229 3229 'hg.usergroup.create.false': 0,
3230 3230 'hg.usergroup.create.true': 1,
3231 3231
3232 3232 'hg.fork.none': 0,
3233 3233 'hg.fork.repository': 1,
3234 3234 'hg.create.none': 0,
3235 3235 'hg.create.repository': 1
3236 3236 }
3237 3237
3238 3238 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3239 3239 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3240 3240 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3241 3241
3242 3242 def __unicode__(self):
3243 3243 return u"<%s('%s:%s')>" % (
3244 3244 self.__class__.__name__, self.permission_id, self.permission_name
3245 3245 )
3246 3246
3247 3247 @classmethod
3248 3248 def get_by_key(cls, key):
3249 3249 return cls.query().filter(cls.permission_name == key).scalar()
3250 3250
3251 3251 @classmethod
3252 3252 def get_default_repo_perms(cls, user_id, repo_id=None):
3253 3253 q = Session().query(UserRepoToPerm, Repository, Permission)\
3254 3254 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3255 3255 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3256 3256 .filter(UserRepoToPerm.user_id == user_id)
3257 3257 if repo_id:
3258 3258 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3259 3259 return q.all()
3260 3260
3261 3261 @classmethod
3262 3262 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3263 3263 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3264 3264 .join(
3265 3265 Permission,
3266 3266 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3267 3267 .join(
3268 3268 UserRepoToPerm,
3269 3269 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3270 3270 .filter(UserRepoToPerm.user_id == user_id)
3271 3271
3272 3272 if repo_id:
3273 3273 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3274 3274 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3275 3275
3276 3276 @classmethod
3277 3277 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3278 3278 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3279 3279 .join(
3280 3280 Permission,
3281 3281 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3282 3282 .join(
3283 3283 Repository,
3284 3284 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3285 3285 .join(
3286 3286 UserGroup,
3287 3287 UserGroupRepoToPerm.users_group_id ==
3288 3288 UserGroup.users_group_id)\
3289 3289 .join(
3290 3290 UserGroupMember,
3291 3291 UserGroupRepoToPerm.users_group_id ==
3292 3292 UserGroupMember.users_group_id)\
3293 3293 .filter(
3294 3294 UserGroupMember.user_id == user_id,
3295 3295 UserGroup.users_group_active == true())
3296 3296 if repo_id:
3297 3297 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3298 3298 return q.all()
3299 3299
3300 3300 @classmethod
3301 3301 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3302 3302 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3303 3303 .join(
3304 3304 Permission,
3305 3305 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3306 3306 .join(
3307 3307 UserGroupRepoToPerm,
3308 3308 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3309 3309 .join(
3310 3310 UserGroup,
3311 3311 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3312 3312 .join(
3313 3313 UserGroupMember,
3314 3314 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3315 3315 .filter(
3316 3316 UserGroupMember.user_id == user_id,
3317 3317 UserGroup.users_group_active == true())
3318 3318
3319 3319 if repo_id:
3320 3320 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3321 3321 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3322 3322
3323 3323 @classmethod
3324 3324 def get_default_group_perms(cls, user_id, repo_group_id=None):
3325 3325 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3326 3326 .join(
3327 3327 Permission,
3328 3328 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3329 3329 .join(
3330 3330 RepoGroup,
3331 3331 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3332 3332 .filter(UserRepoGroupToPerm.user_id == user_id)
3333 3333 if repo_group_id:
3334 3334 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3335 3335 return q.all()
3336 3336
3337 3337 @classmethod
3338 3338 def get_default_group_perms_from_user_group(
3339 3339 cls, user_id, repo_group_id=None):
3340 3340 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3341 3341 .join(
3342 3342 Permission,
3343 3343 UserGroupRepoGroupToPerm.permission_id ==
3344 3344 Permission.permission_id)\
3345 3345 .join(
3346 3346 RepoGroup,
3347 3347 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3348 3348 .join(
3349 3349 UserGroup,
3350 3350 UserGroupRepoGroupToPerm.users_group_id ==
3351 3351 UserGroup.users_group_id)\
3352 3352 .join(
3353 3353 UserGroupMember,
3354 3354 UserGroupRepoGroupToPerm.users_group_id ==
3355 3355 UserGroupMember.users_group_id)\
3356 3356 .filter(
3357 3357 UserGroupMember.user_id == user_id,
3358 3358 UserGroup.users_group_active == true())
3359 3359 if repo_group_id:
3360 3360 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3361 3361 return q.all()
3362 3362
3363 3363 @classmethod
3364 3364 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3365 3365 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3366 3366 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3367 3367 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3368 3368 .filter(UserUserGroupToPerm.user_id == user_id)
3369 3369 if user_group_id:
3370 3370 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3371 3371 return q.all()
3372 3372
3373 3373 @classmethod
3374 3374 def get_default_user_group_perms_from_user_group(
3375 3375 cls, user_id, user_group_id=None):
3376 3376 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3377 3377 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3378 3378 .join(
3379 3379 Permission,
3380 3380 UserGroupUserGroupToPerm.permission_id ==
3381 3381 Permission.permission_id)\
3382 3382 .join(
3383 3383 TargetUserGroup,
3384 3384 UserGroupUserGroupToPerm.target_user_group_id ==
3385 3385 TargetUserGroup.users_group_id)\
3386 3386 .join(
3387 3387 UserGroup,
3388 3388 UserGroupUserGroupToPerm.user_group_id ==
3389 3389 UserGroup.users_group_id)\
3390 3390 .join(
3391 3391 UserGroupMember,
3392 3392 UserGroupUserGroupToPerm.user_group_id ==
3393 3393 UserGroupMember.users_group_id)\
3394 3394 .filter(
3395 3395 UserGroupMember.user_id == user_id,
3396 3396 UserGroup.users_group_active == true())
3397 3397 if user_group_id:
3398 3398 q = q.filter(
3399 3399 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3400 3400
3401 3401 return q.all()
3402 3402
3403 3403
3404 3404 class UserRepoToPerm(Base, BaseModel):
3405 3405 __tablename__ = 'repo_to_perm'
3406 3406 __table_args__ = (
3407 3407 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3408 3408 base_table_args
3409 3409 )
3410 3410
3411 3411 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3412 3412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3413 3413 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3414 3414 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3415 3415
3416 3416 user = relationship('User')
3417 3417 repository = relationship('Repository')
3418 3418 permission = relationship('Permission')
3419 3419
3420 3420 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3421 3421
3422 3422 @classmethod
3423 3423 def create(cls, user, repository, permission):
3424 3424 n = cls()
3425 3425 n.user = user
3426 3426 n.repository = repository
3427 3427 n.permission = permission
3428 3428 Session().add(n)
3429 3429 return n
3430 3430
3431 3431 def __unicode__(self):
3432 3432 return u'<%s => %s >' % (self.user, self.repository)
3433 3433
3434 3434
3435 3435 class UserUserGroupToPerm(Base, BaseModel):
3436 3436 __tablename__ = 'user_user_group_to_perm'
3437 3437 __table_args__ = (
3438 3438 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3439 3439 base_table_args
3440 3440 )
3441 3441
3442 3442 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3443 3443 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3444 3444 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3445 3445 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3446 3446
3447 3447 user = relationship('User')
3448 3448 user_group = relationship('UserGroup')
3449 3449 permission = relationship('Permission')
3450 3450
3451 3451 @classmethod
3452 3452 def create(cls, user, user_group, permission):
3453 3453 n = cls()
3454 3454 n.user = user
3455 3455 n.user_group = user_group
3456 3456 n.permission = permission
3457 3457 Session().add(n)
3458 3458 return n
3459 3459
3460 3460 def __unicode__(self):
3461 3461 return u'<%s => %s >' % (self.user, self.user_group)
3462 3462
3463 3463
3464 3464 class UserToPerm(Base, BaseModel):
3465 3465 __tablename__ = 'user_to_perm'
3466 3466 __table_args__ = (
3467 3467 UniqueConstraint('user_id', 'permission_id'),
3468 3468 base_table_args
3469 3469 )
3470 3470
3471 3471 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3472 3472 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3473 3473 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3474 3474
3475 3475 user = relationship('User')
3476 3476 permission = relationship('Permission', lazy='joined')
3477 3477
3478 3478 def __unicode__(self):
3479 3479 return u'<%s => %s >' % (self.user, self.permission)
3480 3480
3481 3481
3482 3482 class UserGroupRepoToPerm(Base, BaseModel):
3483 3483 __tablename__ = 'users_group_repo_to_perm'
3484 3484 __table_args__ = (
3485 3485 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3486 3486 base_table_args
3487 3487 )
3488 3488
3489 3489 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3490 3490 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3491 3491 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3492 3492 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3493 3493
3494 3494 users_group = relationship('UserGroup')
3495 3495 permission = relationship('Permission')
3496 3496 repository = relationship('Repository')
3497 3497 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3498 3498
3499 3499 @classmethod
3500 3500 def create(cls, users_group, repository, permission):
3501 3501 n = cls()
3502 3502 n.users_group = users_group
3503 3503 n.repository = repository
3504 3504 n.permission = permission
3505 3505 Session().add(n)
3506 3506 return n
3507 3507
3508 3508 def __unicode__(self):
3509 3509 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3510 3510
3511 3511
3512 3512 class UserGroupUserGroupToPerm(Base, BaseModel):
3513 3513 __tablename__ = 'user_group_user_group_to_perm'
3514 3514 __table_args__ = (
3515 3515 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3516 3516 CheckConstraint('target_user_group_id != user_group_id'),
3517 3517 base_table_args
3518 3518 )
3519 3519
3520 3520 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3521 3521 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3522 3522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3523 3523 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3524 3524
3525 3525 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3526 3526 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3527 3527 permission = relationship('Permission')
3528 3528
3529 3529 @classmethod
3530 3530 def create(cls, target_user_group, user_group, permission):
3531 3531 n = cls()
3532 3532 n.target_user_group = target_user_group
3533 3533 n.user_group = user_group
3534 3534 n.permission = permission
3535 3535 Session().add(n)
3536 3536 return n
3537 3537
3538 3538 def __unicode__(self):
3539 3539 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3540 3540
3541 3541
3542 3542 class UserGroupToPerm(Base, BaseModel):
3543 3543 __tablename__ = 'users_group_to_perm'
3544 3544 __table_args__ = (
3545 3545 UniqueConstraint('users_group_id', 'permission_id',),
3546 3546 base_table_args
3547 3547 )
3548 3548
3549 3549 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3550 3550 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3551 3551 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3552 3552
3553 3553 users_group = relationship('UserGroup')
3554 3554 permission = relationship('Permission')
3555 3555
3556 3556
3557 3557 class UserRepoGroupToPerm(Base, BaseModel):
3558 3558 __tablename__ = 'user_repo_group_to_perm'
3559 3559 __table_args__ = (
3560 3560 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3561 3561 base_table_args
3562 3562 )
3563 3563
3564 3564 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3565 3565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3566 3566 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3567 3567 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3568 3568
3569 3569 user = relationship('User')
3570 3570 group = relationship('RepoGroup')
3571 3571 permission = relationship('Permission')
3572 3572
3573 3573 @classmethod
3574 3574 def create(cls, user, repository_group, permission):
3575 3575 n = cls()
3576 3576 n.user = user
3577 3577 n.group = repository_group
3578 3578 n.permission = permission
3579 3579 Session().add(n)
3580 3580 return n
3581 3581
3582 3582
3583 3583 class UserGroupRepoGroupToPerm(Base, BaseModel):
3584 3584 __tablename__ = 'users_group_repo_group_to_perm'
3585 3585 __table_args__ = (
3586 3586 UniqueConstraint('users_group_id', 'group_id'),
3587 3587 base_table_args
3588 3588 )
3589 3589
3590 3590 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3591 3591 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3592 3592 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3593 3593 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3594 3594
3595 3595 users_group = relationship('UserGroup')
3596 3596 permission = relationship('Permission')
3597 3597 group = relationship('RepoGroup')
3598 3598
3599 3599 @classmethod
3600 3600 def create(cls, user_group, repository_group, permission):
3601 3601 n = cls()
3602 3602 n.users_group = user_group
3603 3603 n.group = repository_group
3604 3604 n.permission = permission
3605 3605 Session().add(n)
3606 3606 return n
3607 3607
3608 3608 def __unicode__(self):
3609 3609 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3610 3610
3611 3611
3612 3612 class Statistics(Base, BaseModel):
3613 3613 __tablename__ = 'statistics'
3614 3614 __table_args__ = (
3615 3615 base_table_args
3616 3616 )
3617 3617
3618 3618 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3619 3619 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3620 3620 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3621 3621 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3622 3622 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3623 3623 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3624 3624
3625 3625 repository = relationship('Repository', single_parent=True)
3626 3626
3627 3627
3628 3628 class UserFollowing(Base, BaseModel):
3629 3629 __tablename__ = 'user_followings'
3630 3630 __table_args__ = (
3631 3631 UniqueConstraint('user_id', 'follows_repository_id'),
3632 3632 UniqueConstraint('user_id', 'follows_user_id'),
3633 3633 base_table_args
3634 3634 )
3635 3635
3636 3636 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3637 3637 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3638 3638 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3639 3639 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3640 3640 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3641 3641
3642 3642 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3643 3643
3644 3644 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3645 3645 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3646 3646
3647 3647 @classmethod
3648 3648 def get_repo_followers(cls, repo_id):
3649 3649 return cls.query().filter(cls.follows_repo_id == repo_id)
3650 3650
3651 3651
3652 3652 class CacheKey(Base, BaseModel):
3653 3653 __tablename__ = 'cache_invalidation'
3654 3654 __table_args__ = (
3655 3655 UniqueConstraint('cache_key'),
3656 3656 Index('key_idx', 'cache_key'),
3657 3657 base_table_args,
3658 3658 )
3659 3659
3660 3660 CACHE_TYPE_FEED = 'FEED'
3661 3661
3662 3662 # namespaces used to register process/thread aware caches
3663 3663 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3664 3664 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3665 3665
3666 3666 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3667 3667 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3668 3668 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3669 3669 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3670 3670 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3671 3671
3672 3672 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3673 3673 self.cache_key = cache_key
3674 3674 self.cache_args = cache_args
3675 3675 self.cache_active = False
3676 3676 # first key should be same for all entries, since all workers should share it
3677 3677 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3678 3678
3679 3679 def __unicode__(self):
3680 3680 return u"<%s('%s:%s[%s]')>" % (
3681 3681 self.__class__.__name__,
3682 3682 self.cache_id, self.cache_key, self.cache_active)
3683 3683
3684 3684 def _cache_key_partition(self):
3685 3685 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3686 3686 return prefix, repo_name, suffix
3687 3687
3688 3688 def get_prefix(self):
3689 3689 """
3690 3690 Try to extract prefix from existing cache key. The key could consist
3691 3691 of prefix, repo_name, suffix
3692 3692 """
3693 3693 # this returns prefix, repo_name, suffix
3694 3694 return self._cache_key_partition()[0]
3695 3695
3696 3696 def get_suffix(self):
3697 3697 """
3698 3698 get suffix that might have been used in _get_cache_key to
3699 3699 generate self.cache_key. Only used for informational purposes
3700 3700 in repo_edit.mako.
3701 3701 """
3702 3702 # prefix, repo_name, suffix
3703 3703 return self._cache_key_partition()[2]
3704 3704
3705 3705 @classmethod
3706 3706 def generate_new_state_uid(cls, based_on=None):
3707 3707 if based_on:
3708 3708 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3709 3709 else:
3710 3710 return str(uuid.uuid4())
3711 3711
3712 3712 @classmethod
3713 3713 def delete_all_cache(cls):
3714 3714 """
3715 3715 Delete all cache keys from database.
3716 3716 Should only be run when all instances are down and all entries
3717 3717 thus stale.
3718 3718 """
3719 3719 cls.query().delete()
3720 3720 Session().commit()
3721 3721
3722 3722 @classmethod
3723 3723 def set_invalidate(cls, cache_uid, delete=False):
3724 3724 """
3725 3725 Mark all caches of a repo as invalid in the database.
3726 3726 """
3727 3727
3728 3728 try:
3729 3729 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3730 3730 if delete:
3731 3731 qry.delete()
3732 3732 log.debug('cache objects deleted for cache args %s',
3733 3733 safe_str(cache_uid))
3734 3734 else:
3735 3735 qry.update({"cache_active": False,
3736 3736 "cache_state_uid": cls.generate_new_state_uid()})
3737 3737 log.debug('cache objects marked as invalid for cache args %s',
3738 3738 safe_str(cache_uid))
3739 3739
3740 3740 Session().commit()
3741 3741 except Exception:
3742 3742 log.exception(
3743 3743 'Cache key invalidation failed for cache args %s',
3744 3744 safe_str(cache_uid))
3745 3745 Session().rollback()
3746 3746
3747 3747 @classmethod
3748 3748 def get_active_cache(cls, cache_key):
3749 3749 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3750 3750 if inv_obj:
3751 3751 return inv_obj
3752 3752 return None
3753 3753
3754 3754 @classmethod
3755 3755 def get_namespace_map(cls, namespace):
3756 3756 return {
3757 3757 x.cache_key: x
3758 3758 for x in cls.query().filter(cls.cache_args == namespace)}
3759 3759
3760 3760
3761 3761 class ChangesetComment(Base, BaseModel):
3762 3762 __tablename__ = 'changeset_comments'
3763 3763 __table_args__ = (
3764 3764 Index('cc_revision_idx', 'revision'),
3765 3765 base_table_args,
3766 3766 )
3767 3767
3768 3768 COMMENT_OUTDATED = u'comment_outdated'
3769 3769 COMMENT_TYPE_NOTE = u'note'
3770 3770 COMMENT_TYPE_TODO = u'todo'
3771 3771 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3772 3772
3773 3773 OP_IMMUTABLE = u'immutable'
3774 3774 OP_CHANGEABLE = u'changeable'
3775 3775
3776 3776 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3777 3777 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3778 3778 revision = Column('revision', String(40), nullable=True)
3779 3779 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3780 3780 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3781 3781 line_no = Column('line_no', Unicode(10), nullable=True)
3782 3782 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3783 3783 f_path = Column('f_path', Unicode(1000), nullable=True)
3784 3784 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3785 3785 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3786 3786 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3787 3787 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3788 3788 renderer = Column('renderer', Unicode(64), nullable=True)
3789 3789 display_state = Column('display_state', Unicode(128), nullable=True)
3790 3790 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3791 3791 draft = Column('draft', Boolean(), nullable=True, default=False)
3792 3792
3793 3793 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3794 3794 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3795 3795
3796 3796 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3797 3797 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3798 3798
3799 3799 author = relationship('User', lazy='select')
3800 3800 repo = relationship('Repository')
3801 3801 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select')
3802 3802 pull_request = relationship('PullRequest', lazy='select')
3803 3803 pull_request_version = relationship('PullRequestVersion', lazy='select')
3804 3804 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version')
3805 3805
3806 3806 @classmethod
3807 3807 def get_users(cls, revision=None, pull_request_id=None):
3808 3808 """
3809 3809 Returns user associated with this ChangesetComment. ie those
3810 3810 who actually commented
3811 3811
3812 3812 :param cls:
3813 3813 :param revision:
3814 3814 """
3815 3815 q = Session().query(User)\
3816 3816 .join(ChangesetComment.author)
3817 3817 if revision:
3818 3818 q = q.filter(cls.revision == revision)
3819 3819 elif pull_request_id:
3820 3820 q = q.filter(cls.pull_request_id == pull_request_id)
3821 3821 return q.all()
3822 3822
3823 3823 @classmethod
3824 3824 def get_index_from_version(cls, pr_version, versions=None, num_versions=None):
3825 3825
3826 3826 if versions is not None:
3827 3827 num_versions = [x.pull_request_version_id for x in versions]
3828 3828
3829 3829 num_versions = num_versions or []
3830 3830 try:
3831 3831 return num_versions.index(pr_version) + 1
3832 3832 except (IndexError, ValueError):
3833 3833 return
3834 3834
3835 3835 @property
3836 3836 def outdated(self):
3837 3837 return self.display_state == self.COMMENT_OUTDATED
3838 3838
3839 3839 @property
3840 3840 def outdated_js(self):
3841 3841 return json.dumps(self.display_state == self.COMMENT_OUTDATED)
3842 3842
3843 3843 @property
3844 3844 def immutable(self):
3845 3845 return self.immutable_state == self.OP_IMMUTABLE
3846 3846
3847 3847 def outdated_at_version(self, version):
3848 3848 """
3849 3849 Checks if comment is outdated for given pull request version
3850 3850 """
3851 3851 def version_check():
3852 3852 return self.pull_request_version_id and self.pull_request_version_id != version
3853 3853
3854 3854 if self.is_inline:
3855 3855 return self.outdated and version_check()
3856 3856 else:
3857 3857 # general comments don't have .outdated set, also latest don't have a version
3858 3858 return version_check()
3859 3859
3860 3860 def outdated_at_version_js(self, version):
3861 3861 """
3862 3862 Checks if comment is outdated for given pull request version
3863 3863 """
3864 3864 return json.dumps(self.outdated_at_version(version))
3865 3865
3866 3866 def older_than_version(self, version):
3867 3867 """
3868 3868 Checks if comment is made from previous version than given
3869 3869 """
3870 3870 if version is None:
3871 3871 return self.pull_request_version != version
3872 3872
3873 3873 return self.pull_request_version < version
3874 3874
3875 3875 def older_than_version_js(self, version):
3876 3876 """
3877 3877 Checks if comment is made from previous version than given
3878 3878 """
3879 3879 return json.dumps(self.older_than_version(version))
3880 3880
3881 3881 @property
3882 3882 def commit_id(self):
3883 3883 """New style naming to stop using .revision"""
3884 3884 return self.revision
3885 3885
3886 3886 @property
3887 3887 def resolved(self):
3888 3888 return self.resolved_by[0] if self.resolved_by else None
3889 3889
3890 3890 @property
3891 3891 def is_todo(self):
3892 3892 return self.comment_type == self.COMMENT_TYPE_TODO
3893 3893
3894 3894 @property
3895 3895 def is_inline(self):
3896 3896 if self.line_no and self.f_path:
3897 3897 return True
3898 3898 return False
3899 3899
3900 3900 @property
3901 3901 def last_version(self):
3902 3902 version = 0
3903 3903 if self.history:
3904 3904 version = self.history[-1].version
3905 3905 return version
3906 3906
3907 3907 def get_index_version(self, versions):
3908 3908 return self.get_index_from_version(
3909 3909 self.pull_request_version_id, versions)
3910 3910
3911 3911 @property
3912 3912 def review_status(self):
3913 3913 if self.status_change:
3914 3914 return self.status_change[0].status
3915 3915
3916 3916 @property
3917 3917 def review_status_lbl(self):
3918 3918 if self.status_change:
3919 3919 return self.status_change[0].status_lbl
3920 3920
3921 3921 def __repr__(self):
3922 3922 if self.comment_id:
3923 3923 return '<DB:Comment #%s>' % self.comment_id
3924 3924 else:
3925 3925 return '<DB:Comment at %#x>' % id(self)
3926 3926
3927 3927 def get_api_data(self):
3928 3928 comment = self
3929 3929
3930 3930 data = {
3931 3931 'comment_id': comment.comment_id,
3932 3932 'comment_type': comment.comment_type,
3933 3933 'comment_text': comment.text,
3934 3934 'comment_status': comment.status_change,
3935 3935 'comment_f_path': comment.f_path,
3936 3936 'comment_lineno': comment.line_no,
3937 3937 'comment_author': comment.author,
3938 3938 'comment_created_on': comment.created_on,
3939 3939 'comment_resolved_by': self.resolved,
3940 3940 'comment_commit_id': comment.revision,
3941 3941 'comment_pull_request_id': comment.pull_request_id,
3942 3942 'comment_last_version': self.last_version
3943 3943 }
3944 3944 return data
3945 3945
3946 3946 def __json__(self):
3947 3947 data = dict()
3948 3948 data.update(self.get_api_data())
3949 3949 return data
3950 3950
3951 3951
3952 3952 class ChangesetCommentHistory(Base, BaseModel):
3953 3953 __tablename__ = 'changeset_comments_history'
3954 3954 __table_args__ = (
3955 3955 Index('cch_comment_id_idx', 'comment_id'),
3956 3956 base_table_args,
3957 3957 )
3958 3958
3959 3959 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3960 3960 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3961 3961 version = Column("version", Integer(), nullable=False, default=0)
3962 3962 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3963 3963 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3964 3964 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3965 3965 deleted = Column('deleted', Boolean(), default=False)
3966 3966
3967 3967 author = relationship('User', lazy='joined')
3968 3968 comment = relationship('ChangesetComment', cascade="all, delete")
3969 3969
3970 3970 @classmethod
3971 3971 def get_version(cls, comment_id):
3972 3972 q = Session().query(ChangesetCommentHistory).filter(
3973 3973 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3974 3974 if q.count() == 0:
3975 3975 return 1
3976 3976 elif q.count() >= q[0].version:
3977 3977 return q.count() + 1
3978 3978 else:
3979 3979 return q[0].version + 1
3980 3980
3981 3981
3982 3982 class ChangesetStatus(Base, BaseModel):
3983 3983 __tablename__ = 'changeset_statuses'
3984 3984 __table_args__ = (
3985 3985 Index('cs_revision_idx', 'revision'),
3986 3986 Index('cs_version_idx', 'version'),
3987 3987 UniqueConstraint('repo_id', 'revision', 'version'),
3988 3988 base_table_args
3989 3989 )
3990 3990
3991 3991 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3992 3992 STATUS_APPROVED = 'approved'
3993 3993 STATUS_REJECTED = 'rejected'
3994 3994 STATUS_UNDER_REVIEW = 'under_review'
3995 3995 CheckConstraint,
3996 3996 STATUSES = [
3997 3997 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3998 3998 (STATUS_APPROVED, _("Approved")),
3999 3999 (STATUS_REJECTED, _("Rejected")),
4000 4000 (STATUS_UNDER_REVIEW, _("Under Review")),
4001 4001 ]
4002 4002
4003 4003 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
4004 4004 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4005 4005 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4006 4006 revision = Column('revision', String(40), nullable=False)
4007 4007 status = Column('status', String(128), nullable=False, default=DEFAULT)
4008 4008 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4009 4009 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4010 4010 version = Column('version', Integer(), nullable=False, default=0)
4011 4011 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4012 4012
4013 4013 author = relationship('User', lazy='select')
4014 4014 repo = relationship('Repository', lazy='select')
4015 4015 comment = relationship('ChangesetComment', lazy='select')
4016 4016 pull_request = relationship('PullRequest', lazy='select')
4017 4017
4018 4018 def __unicode__(self):
4019 4019 return u"<%s('%s[v%s]:%s')>" % (
4020 4020 self.__class__.__name__,
4021 4021 self.status, self.version, self.author
4022 4022 )
4023 4023
4024 4024 @classmethod
4025 4025 def get_status_lbl(cls, value):
4026 4026 return dict(cls.STATUSES).get(value)
4027 4027
4028 4028 @property
4029 4029 def status_lbl(self):
4030 4030 return ChangesetStatus.get_status_lbl(self.status)
4031 4031
4032 4032 def get_api_data(self):
4033 4033 status = self
4034 4034 data = {
4035 4035 'status_id': status.changeset_status_id,
4036 4036 'status': status.status,
4037 4037 }
4038 4038 return data
4039 4039
4040 4040 def __json__(self):
4041 4041 data = dict()
4042 4042 data.update(self.get_api_data())
4043 4043 return data
4044 4044
4045 4045
4046 4046 class _SetState(object):
4047 4047 """
4048 4048 Context processor allowing changing state for sensitive operation such as
4049 4049 pull request update or merge
4050 4050 """
4051 4051
4052 4052 def __init__(self, pull_request, pr_state, back_state=None):
4053 4053 self._pr = pull_request
4054 4054 self._org_state = back_state or pull_request.pull_request_state
4055 4055 self._pr_state = pr_state
4056 4056 self._current_state = None
4057 4057
4058 4058 def __enter__(self):
4059 4059 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4060 4060 self._pr, self._pr_state)
4061 4061 self.set_pr_state(self._pr_state)
4062 4062 return self
4063 4063
4064 4064 def __exit__(self, exc_type, exc_val, exc_tb):
4065 4065 if exc_val is not None:
4066 4066 log.error(traceback.format_exc(exc_tb))
4067 4067 return None
4068 4068
4069 4069 self.set_pr_state(self._org_state)
4070 4070 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4071 4071 self._pr, self._org_state)
4072 4072
4073 4073 @property
4074 4074 def state(self):
4075 4075 return self._current_state
4076 4076
4077 4077 def set_pr_state(self, pr_state):
4078 4078 try:
4079 4079 self._pr.pull_request_state = pr_state
4080 4080 Session().add(self._pr)
4081 4081 Session().commit()
4082 4082 self._current_state = pr_state
4083 4083 except Exception:
4084 4084 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4085 4085 raise
4086 4086
4087 4087
4088 4088 class _PullRequestBase(BaseModel):
4089 4089 """
4090 4090 Common attributes of pull request and version entries.
4091 4091 """
4092 4092
4093 4093 # .status values
4094 4094 STATUS_NEW = u'new'
4095 4095 STATUS_OPEN = u'open'
4096 4096 STATUS_CLOSED = u'closed'
4097 4097
4098 4098 # available states
4099 4099 STATE_CREATING = u'creating'
4100 4100 STATE_UPDATING = u'updating'
4101 4101 STATE_MERGING = u'merging'
4102 4102 STATE_CREATED = u'created'
4103 4103
4104 4104 title = Column('title', Unicode(255), nullable=True)
4105 4105 description = Column(
4106 4106 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4107 4107 nullable=True)
4108 4108 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4109 4109
4110 4110 # new/open/closed status of pull request (not approve/reject/etc)
4111 4111 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4112 4112 created_on = Column(
4113 4113 'created_on', DateTime(timezone=False), nullable=False,
4114 4114 default=datetime.datetime.now)
4115 4115 updated_on = Column(
4116 4116 'updated_on', DateTime(timezone=False), nullable=False,
4117 4117 default=datetime.datetime.now)
4118 4118
4119 4119 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4120 4120
4121 4121 @declared_attr
4122 4122 def user_id(cls):
4123 4123 return Column(
4124 4124 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4125 4125 unique=None)
4126 4126
4127 4127 # 500 revisions max
4128 4128 _revisions = Column(
4129 4129 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4130 4130
4131 4131 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4132 4132
4133 4133 @declared_attr
4134 4134 def source_repo_id(cls):
4135 4135 # TODO: dan: rename column to source_repo_id
4136 4136 return Column(
4137 4137 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4138 4138 nullable=False)
4139 4139
4140 4140 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4141 4141
4142 4142 @hybrid_property
4143 4143 def source_ref(self):
4144 4144 return self._source_ref
4145 4145
4146 4146 @source_ref.setter
4147 4147 def source_ref(self, val):
4148 4148 parts = (val or '').split(':')
4149 4149 if len(parts) != 3:
4150 4150 raise ValueError(
4151 4151 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4152 4152 self._source_ref = safe_unicode(val)
4153 4153
4154 4154 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4155 4155
4156 4156 @hybrid_property
4157 4157 def target_ref(self):
4158 4158 return self._target_ref
4159 4159
4160 4160 @target_ref.setter
4161 4161 def target_ref(self, val):
4162 4162 parts = (val or '').split(':')
4163 4163 if len(parts) != 3:
4164 4164 raise ValueError(
4165 4165 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4166 4166 self._target_ref = safe_unicode(val)
4167 4167
4168 4168 @declared_attr
4169 4169 def target_repo_id(cls):
4170 4170 # TODO: dan: rename column to target_repo_id
4171 4171 return Column(
4172 4172 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4173 4173 nullable=False)
4174 4174
4175 4175 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4176 4176
4177 4177 # TODO: dan: rename column to last_merge_source_rev
4178 4178 _last_merge_source_rev = Column(
4179 4179 'last_merge_org_rev', String(40), nullable=True)
4180 4180 # TODO: dan: rename column to last_merge_target_rev
4181 4181 _last_merge_target_rev = Column(
4182 4182 'last_merge_other_rev', String(40), nullable=True)
4183 4183 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4184 4184 last_merge_metadata = Column(
4185 4185 'last_merge_metadata', MutationObj.as_mutable(
4186 4186 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4187 4187
4188 4188 merge_rev = Column('merge_rev', String(40), nullable=True)
4189 4189
4190 4190 reviewer_data = Column(
4191 4191 'reviewer_data_json', MutationObj.as_mutable(
4192 4192 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4193 4193
4194 4194 @property
4195 4195 def reviewer_data_json(self):
4196 4196 return json.dumps(self.reviewer_data)
4197 4197
4198 4198 @property
4199 4199 def last_merge_metadata_parsed(self):
4200 4200 metadata = {}
4201 4201 if not self.last_merge_metadata:
4202 4202 return metadata
4203 4203
4204 4204 if hasattr(self.last_merge_metadata, 'de_coerce'):
4205 4205 for k, v in self.last_merge_metadata.de_coerce().items():
4206 4206 if k in ['target_ref', 'source_ref']:
4207 4207 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4208 4208 else:
4209 4209 if hasattr(v, 'de_coerce'):
4210 4210 metadata[k] = v.de_coerce()
4211 4211 else:
4212 4212 metadata[k] = v
4213 4213 return metadata
4214 4214
4215 4215 @property
4216 4216 def work_in_progress(self):
4217 4217 """checks if pull request is work in progress by checking the title"""
4218 4218 title = self.title.upper()
4219 4219 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4220 4220 return True
4221 4221 return False
4222 4222
4223 @property
4224 def title_safe(self):
4225 return self.title\
4226 .replace('{', '{{')\
4227 .replace('}', '}}')
4228
4223 4229 @hybrid_property
4224 4230 def description_safe(self):
4225 4231 from rhodecode.lib import helpers as h
4226 4232 return h.escape(self.description)
4227 4233
4228 4234 @hybrid_property
4229 4235 def revisions(self):
4230 4236 return self._revisions.split(':') if self._revisions else []
4231 4237
4232 4238 @revisions.setter
4233 4239 def revisions(self, val):
4234 4240 self._revisions = u':'.join(val)
4235 4241
4236 4242 @hybrid_property
4237 4243 def last_merge_status(self):
4238 4244 return safe_int(self._last_merge_status)
4239 4245
4240 4246 @last_merge_status.setter
4241 4247 def last_merge_status(self, val):
4242 4248 self._last_merge_status = val
4243 4249
4244 4250 @declared_attr
4245 4251 def author(cls):
4246 4252 return relationship('User', lazy='joined')
4247 4253
4248 4254 @declared_attr
4249 4255 def source_repo(cls):
4250 4256 return relationship(
4251 4257 'Repository',
4252 4258 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4253 4259
4254 4260 @property
4255 4261 def source_ref_parts(self):
4256 4262 return self.unicode_to_reference(self.source_ref)
4257 4263
4258 4264 @declared_attr
4259 4265 def target_repo(cls):
4260 4266 return relationship(
4261 4267 'Repository',
4262 4268 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4263 4269
4264 4270 @property
4265 4271 def target_ref_parts(self):
4266 4272 return self.unicode_to_reference(self.target_ref)
4267 4273
4268 4274 @property
4269 4275 def shadow_merge_ref(self):
4270 4276 return self.unicode_to_reference(self._shadow_merge_ref)
4271 4277
4272 4278 @shadow_merge_ref.setter
4273 4279 def shadow_merge_ref(self, ref):
4274 4280 self._shadow_merge_ref = self.reference_to_unicode(ref)
4275 4281
4276 4282 @staticmethod
4277 4283 def unicode_to_reference(raw):
4278 4284 return unicode_to_reference(raw)
4279 4285
4280 4286 @staticmethod
4281 4287 def reference_to_unicode(ref):
4282 4288 return reference_to_unicode(ref)
4283 4289
4284 4290 def get_api_data(self, with_merge_state=True):
4285 4291 from rhodecode.model.pull_request import PullRequestModel
4286 4292
4287 4293 pull_request = self
4288 4294 if with_merge_state:
4289 4295 merge_response, merge_status, msg = \
4290 4296 PullRequestModel().merge_status(pull_request)
4291 4297 merge_state = {
4292 4298 'status': merge_status,
4293 4299 'message': safe_unicode(msg),
4294 4300 }
4295 4301 else:
4296 4302 merge_state = {'status': 'not_available',
4297 4303 'message': 'not_available'}
4298 4304
4299 4305 merge_data = {
4300 4306 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4301 4307 'reference': (
4302 4308 pull_request.shadow_merge_ref._asdict()
4303 4309 if pull_request.shadow_merge_ref else None),
4304 4310 }
4305 4311
4306 4312 data = {
4307 4313 'pull_request_id': pull_request.pull_request_id,
4308 4314 'url': PullRequestModel().get_url(pull_request),
4309 4315 'title': pull_request.title,
4310 4316 'description': pull_request.description,
4311 4317 'status': pull_request.status,
4312 4318 'state': pull_request.pull_request_state,
4313 4319 'created_on': pull_request.created_on,
4314 4320 'updated_on': pull_request.updated_on,
4315 4321 'commit_ids': pull_request.revisions,
4316 4322 'review_status': pull_request.calculated_review_status(),
4317 4323 'mergeable': merge_state,
4318 4324 'source': {
4319 4325 'clone_url': pull_request.source_repo.clone_url(),
4320 4326 'repository': pull_request.source_repo.repo_name,
4321 4327 'reference': {
4322 4328 'name': pull_request.source_ref_parts.name,
4323 4329 'type': pull_request.source_ref_parts.type,
4324 4330 'commit_id': pull_request.source_ref_parts.commit_id,
4325 4331 },
4326 4332 },
4327 4333 'target': {
4328 4334 'clone_url': pull_request.target_repo.clone_url(),
4329 4335 'repository': pull_request.target_repo.repo_name,
4330 4336 'reference': {
4331 4337 'name': pull_request.target_ref_parts.name,
4332 4338 'type': pull_request.target_ref_parts.type,
4333 4339 'commit_id': pull_request.target_ref_parts.commit_id,
4334 4340 },
4335 4341 },
4336 4342 'merge': merge_data,
4337 4343 'author': pull_request.author.get_api_data(include_secrets=False,
4338 4344 details='basic'),
4339 4345 'reviewers': [
4340 4346 {
4341 4347 'user': reviewer.get_api_data(include_secrets=False,
4342 4348 details='basic'),
4343 4349 'reasons': reasons,
4344 4350 'review_status': st[0][1].status if st else 'not_reviewed',
4345 4351 }
4346 4352 for obj, reviewer, reasons, mandatory, st in
4347 4353 pull_request.reviewers_statuses()
4348 4354 ]
4349 4355 }
4350 4356
4351 4357 return data
4352 4358
4353 4359 def set_state(self, pull_request_state, final_state=None):
4354 4360 """
4355 4361 # goes from initial state to updating to initial state.
4356 4362 # initial state can be changed by specifying back_state=
4357 4363 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4358 4364 pull_request.merge()
4359 4365
4360 4366 :param pull_request_state:
4361 4367 :param final_state:
4362 4368
4363 4369 """
4364 4370
4365 4371 return _SetState(self, pull_request_state, back_state=final_state)
4366 4372
4367 4373
4368 4374 class PullRequest(Base, _PullRequestBase):
4369 4375 __tablename__ = 'pull_requests'
4370 4376 __table_args__ = (
4371 4377 base_table_args,
4372 4378 )
4373 4379 LATEST_VER = 'latest'
4374 4380
4375 4381 pull_request_id = Column(
4376 4382 'pull_request_id', Integer(), nullable=False, primary_key=True)
4377 4383
4378 4384 def __repr__(self):
4379 4385 if self.pull_request_id:
4380 4386 return '<DB:PullRequest #%s>' % self.pull_request_id
4381 4387 else:
4382 4388 return '<DB:PullRequest at %#x>' % id(self)
4383 4389
4384 4390 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4385 4391 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4386 4392 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4387 4393 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4388 4394 lazy='dynamic')
4389 4395
4390 4396 @classmethod
4391 4397 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4392 4398 internal_methods=None):
4393 4399
4394 4400 class PullRequestDisplay(object):
4395 4401 """
4396 4402 Special object wrapper for showing PullRequest data via Versions
4397 4403 It mimics PR object as close as possible. This is read only object
4398 4404 just for display
4399 4405 """
4400 4406
4401 4407 def __init__(self, attrs, internal=None):
4402 4408 self.attrs = attrs
4403 4409 # internal have priority over the given ones via attrs
4404 4410 self.internal = internal or ['versions']
4405 4411
4406 4412 def __getattr__(self, item):
4407 4413 if item in self.internal:
4408 4414 return getattr(self, item)
4409 4415 try:
4410 4416 return self.attrs[item]
4411 4417 except KeyError:
4412 4418 raise AttributeError(
4413 4419 '%s object has no attribute %s' % (self, item))
4414 4420
4415 4421 def __repr__(self):
4416 4422 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4417 4423
4418 4424 def versions(self):
4419 4425 return pull_request_obj.versions.order_by(
4420 4426 PullRequestVersion.pull_request_version_id).all()
4421 4427
4422 4428 def is_closed(self):
4423 4429 return pull_request_obj.is_closed()
4424 4430
4425 4431 def is_state_changing(self):
4426 4432 return pull_request_obj.is_state_changing()
4427 4433
4428 4434 @property
4429 4435 def pull_request_version_id(self):
4430 4436 return getattr(pull_request_obj, 'pull_request_version_id', None)
4431 4437
4432 4438 @property
4433 4439 def pull_request_last_version(self):
4434 4440 return pull_request_obj.pull_request_last_version
4435 4441
4436 4442 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4437 4443
4438 4444 attrs.author = StrictAttributeDict(
4439 4445 pull_request_obj.author.get_api_data())
4440 4446 if pull_request_obj.target_repo:
4441 4447 attrs.target_repo = StrictAttributeDict(
4442 4448 pull_request_obj.target_repo.get_api_data())
4443 4449 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4444 4450
4445 4451 if pull_request_obj.source_repo:
4446 4452 attrs.source_repo = StrictAttributeDict(
4447 4453 pull_request_obj.source_repo.get_api_data())
4448 4454 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4449 4455
4450 4456 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4451 4457 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4452 4458 attrs.revisions = pull_request_obj.revisions
4453 4459 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4454 4460 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4455 4461 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4456 4462 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4457 4463
4458 4464 return PullRequestDisplay(attrs, internal=internal_methods)
4459 4465
4460 4466 def is_closed(self):
4461 4467 return self.status == self.STATUS_CLOSED
4462 4468
4463 4469 def is_state_changing(self):
4464 4470 return self.pull_request_state != PullRequest.STATE_CREATED
4465 4471
4466 4472 def __json__(self):
4467 4473 return {
4468 4474 'revisions': self.revisions,
4469 4475 'versions': self.versions_count
4470 4476 }
4471 4477
4472 4478 def calculated_review_status(self):
4473 4479 from rhodecode.model.changeset_status import ChangesetStatusModel
4474 4480 return ChangesetStatusModel().calculated_review_status(self)
4475 4481
4476 4482 def reviewers_statuses(self):
4477 4483 from rhodecode.model.changeset_status import ChangesetStatusModel
4478 4484 return ChangesetStatusModel().reviewers_statuses(self)
4479 4485
4480 4486 def get_pull_request_reviewers(self, role=None):
4481 4487 qry = PullRequestReviewers.query()\
4482 4488 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4483 4489 if role:
4484 4490 qry = qry.filter(PullRequestReviewers.role == role)
4485 4491
4486 4492 return qry.all()
4487 4493
4488 4494 @property
4489 4495 def reviewers_count(self):
4490 4496 qry = PullRequestReviewers.query()\
4491 4497 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4492 4498 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4493 4499 return qry.count()
4494 4500
4495 4501 @property
4496 4502 def observers_count(self):
4497 4503 qry = PullRequestReviewers.query()\
4498 4504 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4499 4505 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4500 4506 return qry.count()
4501 4507
4502 4508 def observers(self):
4503 4509 qry = PullRequestReviewers.query()\
4504 4510 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4505 4511 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4506 4512 .all()
4507 4513
4508 4514 for entry in qry:
4509 4515 yield entry, entry.user
4510 4516
4511 4517 @property
4512 4518 def workspace_id(self):
4513 4519 from rhodecode.model.pull_request import PullRequestModel
4514 4520 return PullRequestModel()._workspace_id(self)
4515 4521
4516 4522 def get_shadow_repo(self):
4517 4523 workspace_id = self.workspace_id
4518 4524 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4519 4525 if os.path.isdir(shadow_repository_path):
4520 4526 vcs_obj = self.target_repo.scm_instance()
4521 4527 return vcs_obj.get_shadow_instance(shadow_repository_path)
4522 4528
4523 4529 @property
4524 4530 def versions_count(self):
4525 4531 """
4526 4532 return number of versions this PR have, e.g a PR that once been
4527 4533 updated will have 2 versions
4528 4534 """
4529 4535 return self.versions.count() + 1
4530 4536
4531 4537 @property
4532 4538 def pull_request_last_version(self):
4533 4539 return self.versions_count
4534 4540
4535 4541
4536 4542 class PullRequestVersion(Base, _PullRequestBase):
4537 4543 __tablename__ = 'pull_request_versions'
4538 4544 __table_args__ = (
4539 4545 base_table_args,
4540 4546 )
4541 4547
4542 4548 pull_request_version_id = Column(
4543 4549 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4544 4550 pull_request_id = Column(
4545 4551 'pull_request_id', Integer(),
4546 4552 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4547 4553 pull_request = relationship('PullRequest')
4548 4554
4549 4555 def __repr__(self):
4550 4556 if self.pull_request_version_id:
4551 4557 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4552 4558 else:
4553 4559 return '<DB:PullRequestVersion at %#x>' % id(self)
4554 4560
4555 4561 @property
4556 4562 def reviewers(self):
4557 4563 return self.pull_request.reviewers
4558 4564 @property
4559 4565 def reviewers(self):
4560 4566 return self.pull_request.reviewers
4561 4567
4562 4568 @property
4563 4569 def versions(self):
4564 4570 return self.pull_request.versions
4565 4571
4566 4572 def is_closed(self):
4567 4573 # calculate from original
4568 4574 return self.pull_request.status == self.STATUS_CLOSED
4569 4575
4570 4576 def is_state_changing(self):
4571 4577 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4572 4578
4573 4579 def calculated_review_status(self):
4574 4580 return self.pull_request.calculated_review_status()
4575 4581
4576 4582 def reviewers_statuses(self):
4577 4583 return self.pull_request.reviewers_statuses()
4578 4584
4579 4585 def observers(self):
4580 4586 return self.pull_request.observers()
4581 4587
4582 4588
4583 4589 class PullRequestReviewers(Base, BaseModel):
4584 4590 __tablename__ = 'pull_request_reviewers'
4585 4591 __table_args__ = (
4586 4592 base_table_args,
4587 4593 )
4588 4594 ROLE_REVIEWER = u'reviewer'
4589 4595 ROLE_OBSERVER = u'observer'
4590 4596 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4591 4597
4592 4598 @hybrid_property
4593 4599 def reasons(self):
4594 4600 if not self._reasons:
4595 4601 return []
4596 4602 return self._reasons
4597 4603
4598 4604 @reasons.setter
4599 4605 def reasons(self, val):
4600 4606 val = val or []
4601 4607 if any(not isinstance(x, compat.string_types) for x in val):
4602 4608 raise Exception('invalid reasons type, must be list of strings')
4603 4609 self._reasons = val
4604 4610
4605 4611 pull_requests_reviewers_id = Column(
4606 4612 'pull_requests_reviewers_id', Integer(), nullable=False,
4607 4613 primary_key=True)
4608 4614 pull_request_id = Column(
4609 4615 "pull_request_id", Integer(),
4610 4616 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4611 4617 user_id = Column(
4612 4618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4613 4619 _reasons = Column(
4614 4620 'reason', MutationList.as_mutable(
4615 4621 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4616 4622
4617 4623 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4618 4624 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4619 4625
4620 4626 user = relationship('User')
4621 4627 pull_request = relationship('PullRequest')
4622 4628
4623 4629 rule_data = Column(
4624 4630 'rule_data_json',
4625 4631 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4626 4632
4627 4633 def rule_user_group_data(self):
4628 4634 """
4629 4635 Returns the voting user group rule data for this reviewer
4630 4636 """
4631 4637
4632 4638 if self.rule_data and 'vote_rule' in self.rule_data:
4633 4639 user_group_data = {}
4634 4640 if 'rule_user_group_entry_id' in self.rule_data:
4635 4641 # means a group with voting rules !
4636 4642 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4637 4643 user_group_data['name'] = self.rule_data['rule_name']
4638 4644 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4639 4645
4640 4646 return user_group_data
4641 4647
4642 4648 @classmethod
4643 4649 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4644 4650 qry = PullRequestReviewers.query()\
4645 4651 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4646 4652 if role:
4647 4653 qry = qry.filter(PullRequestReviewers.role == role)
4648 4654
4649 4655 return qry.all()
4650 4656
4651 4657 def __unicode__(self):
4652 4658 return u"<%s('id:%s')>" % (self.__class__.__name__,
4653 4659 self.pull_requests_reviewers_id)
4654 4660
4655 4661
4656 4662 class Notification(Base, BaseModel):
4657 4663 __tablename__ = 'notifications'
4658 4664 __table_args__ = (
4659 4665 Index('notification_type_idx', 'type'),
4660 4666 base_table_args,
4661 4667 )
4662 4668
4663 4669 TYPE_CHANGESET_COMMENT = u'cs_comment'
4664 4670 TYPE_MESSAGE = u'message'
4665 4671 TYPE_MENTION = u'mention'
4666 4672 TYPE_REGISTRATION = u'registration'
4667 4673 TYPE_PULL_REQUEST = u'pull_request'
4668 4674 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4669 4675 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4670 4676
4671 4677 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4672 4678 subject = Column('subject', Unicode(512), nullable=True)
4673 4679 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4674 4680 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4675 4681 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4676 4682 type_ = Column('type', Unicode(255))
4677 4683
4678 4684 created_by_user = relationship('User')
4679 4685 notifications_to_users = relationship('UserNotification', lazy='joined',
4680 4686 cascade="all, delete-orphan")
4681 4687
4682 4688 @property
4683 4689 def recipients(self):
4684 4690 return [x.user for x in UserNotification.query()\
4685 4691 .filter(UserNotification.notification == self)\
4686 4692 .order_by(UserNotification.user_id.asc()).all()]
4687 4693
4688 4694 @classmethod
4689 4695 def create(cls, created_by, subject, body, recipients, type_=None):
4690 4696 if type_ is None:
4691 4697 type_ = Notification.TYPE_MESSAGE
4692 4698
4693 4699 notification = cls()
4694 4700 notification.created_by_user = created_by
4695 4701 notification.subject = subject
4696 4702 notification.body = body
4697 4703 notification.type_ = type_
4698 4704 notification.created_on = datetime.datetime.now()
4699 4705
4700 4706 # For each recipient link the created notification to his account
4701 4707 for u in recipients:
4702 4708 assoc = UserNotification()
4703 4709 assoc.user_id = u.user_id
4704 4710 assoc.notification = notification
4705 4711
4706 4712 # if created_by is inside recipients mark his notification
4707 4713 # as read
4708 4714 if u.user_id == created_by.user_id:
4709 4715 assoc.read = True
4710 4716 Session().add(assoc)
4711 4717
4712 4718 Session().add(notification)
4713 4719
4714 4720 return notification
4715 4721
4716 4722
4717 4723 class UserNotification(Base, BaseModel):
4718 4724 __tablename__ = 'user_to_notification'
4719 4725 __table_args__ = (
4720 4726 UniqueConstraint('user_id', 'notification_id'),
4721 4727 base_table_args
4722 4728 )
4723 4729
4724 4730 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4725 4731 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4726 4732 read = Column('read', Boolean, default=False)
4727 4733 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4728 4734
4729 4735 user = relationship('User', lazy="joined")
4730 4736 notification = relationship('Notification', lazy="joined",
4731 4737 order_by=lambda: Notification.created_on.desc(),)
4732 4738
4733 4739 def mark_as_read(self):
4734 4740 self.read = True
4735 4741 Session().add(self)
4736 4742
4737 4743
4738 4744 class UserNotice(Base, BaseModel):
4739 4745 __tablename__ = 'user_notices'
4740 4746 __table_args__ = (
4741 4747 base_table_args
4742 4748 )
4743 4749
4744 4750 NOTIFICATION_TYPE_MESSAGE = 'message'
4745 4751 NOTIFICATION_TYPE_NOTICE = 'notice'
4746 4752
4747 4753 NOTIFICATION_LEVEL_INFO = 'info'
4748 4754 NOTIFICATION_LEVEL_WARNING = 'warning'
4749 4755 NOTIFICATION_LEVEL_ERROR = 'error'
4750 4756
4751 4757 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4752 4758
4753 4759 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4754 4760 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4755 4761
4756 4762 notice_read = Column('notice_read', Boolean, default=False)
4757 4763
4758 4764 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4759 4765 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4760 4766
4761 4767 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4762 4768 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4763 4769
4764 4770 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4765 4771 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4766 4772
4767 4773 @classmethod
4768 4774 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4769 4775
4770 4776 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4771 4777 cls.NOTIFICATION_LEVEL_WARNING,
4772 4778 cls.NOTIFICATION_LEVEL_INFO]:
4773 4779 return
4774 4780
4775 4781 from rhodecode.model.user import UserModel
4776 4782 user = UserModel().get_user(user)
4777 4783
4778 4784 new_notice = UserNotice()
4779 4785 if not allow_duplicate:
4780 4786 existing_msg = UserNotice().query() \
4781 4787 .filter(UserNotice.user == user) \
4782 4788 .filter(UserNotice.notice_body == body) \
4783 4789 .filter(UserNotice.notice_read == false()) \
4784 4790 .scalar()
4785 4791 if existing_msg:
4786 4792 log.warning('Ignoring duplicate notice for user %s', user)
4787 4793 return
4788 4794
4789 4795 new_notice.user = user
4790 4796 new_notice.notice_subject = subject
4791 4797 new_notice.notice_body = body
4792 4798 new_notice.notification_level = notice_level
4793 4799 Session().add(new_notice)
4794 4800 Session().commit()
4795 4801
4796 4802
4797 4803 class Gist(Base, BaseModel):
4798 4804 __tablename__ = 'gists'
4799 4805 __table_args__ = (
4800 4806 Index('g_gist_access_id_idx', 'gist_access_id'),
4801 4807 Index('g_created_on_idx', 'created_on'),
4802 4808 base_table_args
4803 4809 )
4804 4810
4805 4811 GIST_PUBLIC = u'public'
4806 4812 GIST_PRIVATE = u'private'
4807 4813 DEFAULT_FILENAME = u'gistfile1.txt'
4808 4814
4809 4815 ACL_LEVEL_PUBLIC = u'acl_public'
4810 4816 ACL_LEVEL_PRIVATE = u'acl_private'
4811 4817
4812 4818 gist_id = Column('gist_id', Integer(), primary_key=True)
4813 4819 gist_access_id = Column('gist_access_id', Unicode(250))
4814 4820 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4815 4821 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4816 4822 gist_expires = Column('gist_expires', Float(53), nullable=False)
4817 4823 gist_type = Column('gist_type', Unicode(128), nullable=False)
4818 4824 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4819 4825 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4820 4826 acl_level = Column('acl_level', Unicode(128), nullable=True)
4821 4827
4822 4828 owner = relationship('User')
4823 4829
4824 4830 def __repr__(self):
4825 4831 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4826 4832
4827 4833 @hybrid_property
4828 4834 def description_safe(self):
4829 4835 from rhodecode.lib import helpers as h
4830 4836 return h.escape(self.gist_description)
4831 4837
4832 4838 @classmethod
4833 4839 def get_or_404(cls, id_):
4834 4840 from pyramid.httpexceptions import HTTPNotFound
4835 4841
4836 4842 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4837 4843 if not res:
4838 4844 log.debug('WARN: No DB entry with id %s', id_)
4839 4845 raise HTTPNotFound()
4840 4846 return res
4841 4847
4842 4848 @classmethod
4843 4849 def get_by_access_id(cls, gist_access_id):
4844 4850 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4845 4851
4846 4852 def gist_url(self):
4847 4853 from rhodecode.model.gist import GistModel
4848 4854 return GistModel().get_url(self)
4849 4855
4850 4856 @classmethod
4851 4857 def base_path(cls):
4852 4858 """
4853 4859 Returns base path when all gists are stored
4854 4860
4855 4861 :param cls:
4856 4862 """
4857 4863 from rhodecode.model.gist import GIST_STORE_LOC
4858 4864 q = Session().query(RhodeCodeUi)\
4859 4865 .filter(RhodeCodeUi.ui_key == URL_SEP)
4860 4866 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4861 4867 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4862 4868
4863 4869 def get_api_data(self):
4864 4870 """
4865 4871 Common function for generating gist related data for API
4866 4872 """
4867 4873 gist = self
4868 4874 data = {
4869 4875 'gist_id': gist.gist_id,
4870 4876 'type': gist.gist_type,
4871 4877 'access_id': gist.gist_access_id,
4872 4878 'description': gist.gist_description,
4873 4879 'url': gist.gist_url(),
4874 4880 'expires': gist.gist_expires,
4875 4881 'created_on': gist.created_on,
4876 4882 'modified_at': gist.modified_at,
4877 4883 'content': None,
4878 4884 'acl_level': gist.acl_level,
4879 4885 }
4880 4886 return data
4881 4887
4882 4888 def __json__(self):
4883 4889 data = dict(
4884 4890 )
4885 4891 data.update(self.get_api_data())
4886 4892 return data
4887 4893 # SCM functions
4888 4894
4889 4895 def scm_instance(self, **kwargs):
4890 4896 """
4891 4897 Get an instance of VCS Repository
4892 4898
4893 4899 :param kwargs:
4894 4900 """
4895 4901 from rhodecode.model.gist import GistModel
4896 4902 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4897 4903 return get_vcs_instance(
4898 4904 repo_path=safe_str(full_repo_path), create=False,
4899 4905 _vcs_alias=GistModel.vcs_backend)
4900 4906
4901 4907
4902 4908 class ExternalIdentity(Base, BaseModel):
4903 4909 __tablename__ = 'external_identities'
4904 4910 __table_args__ = (
4905 4911 Index('local_user_id_idx', 'local_user_id'),
4906 4912 Index('external_id_idx', 'external_id'),
4907 4913 base_table_args
4908 4914 )
4909 4915
4910 4916 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4911 4917 external_username = Column('external_username', Unicode(1024), default=u'')
4912 4918 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4913 4919 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4914 4920 access_token = Column('access_token', String(1024), default=u'')
4915 4921 alt_token = Column('alt_token', String(1024), default=u'')
4916 4922 token_secret = Column('token_secret', String(1024), default=u'')
4917 4923
4918 4924 @classmethod
4919 4925 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4920 4926 """
4921 4927 Returns ExternalIdentity instance based on search params
4922 4928
4923 4929 :param external_id:
4924 4930 :param provider_name:
4925 4931 :return: ExternalIdentity
4926 4932 """
4927 4933 query = cls.query()
4928 4934 query = query.filter(cls.external_id == external_id)
4929 4935 query = query.filter(cls.provider_name == provider_name)
4930 4936 if local_user_id:
4931 4937 query = query.filter(cls.local_user_id == local_user_id)
4932 4938 return query.first()
4933 4939
4934 4940 @classmethod
4935 4941 def user_by_external_id_and_provider(cls, external_id, provider_name):
4936 4942 """
4937 4943 Returns User instance based on search params
4938 4944
4939 4945 :param external_id:
4940 4946 :param provider_name:
4941 4947 :return: User
4942 4948 """
4943 4949 query = User.query()
4944 4950 query = query.filter(cls.external_id == external_id)
4945 4951 query = query.filter(cls.provider_name == provider_name)
4946 4952 query = query.filter(User.user_id == cls.local_user_id)
4947 4953 return query.first()
4948 4954
4949 4955 @classmethod
4950 4956 def by_local_user_id(cls, local_user_id):
4951 4957 """
4952 4958 Returns all tokens for user
4953 4959
4954 4960 :param local_user_id:
4955 4961 :return: ExternalIdentity
4956 4962 """
4957 4963 query = cls.query()
4958 4964 query = query.filter(cls.local_user_id == local_user_id)
4959 4965 return query
4960 4966
4961 4967 @classmethod
4962 4968 def load_provider_plugin(cls, plugin_id):
4963 4969 from rhodecode.authentication.base import loadplugin
4964 4970 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4965 4971 auth_plugin = loadplugin(_plugin_id)
4966 4972 return auth_plugin
4967 4973
4968 4974
4969 4975 class Integration(Base, BaseModel):
4970 4976 __tablename__ = 'integrations'
4971 4977 __table_args__ = (
4972 4978 base_table_args
4973 4979 )
4974 4980
4975 4981 integration_id = Column('integration_id', Integer(), primary_key=True)
4976 4982 integration_type = Column('integration_type', String(255))
4977 4983 enabled = Column('enabled', Boolean(), nullable=False)
4978 4984 name = Column('name', String(255), nullable=False)
4979 4985 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4980 4986 default=False)
4981 4987
4982 4988 settings = Column(
4983 4989 'settings_json', MutationObj.as_mutable(
4984 4990 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4985 4991 repo_id = Column(
4986 4992 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4987 4993 nullable=True, unique=None, default=None)
4988 4994 repo = relationship('Repository', lazy='joined')
4989 4995
4990 4996 repo_group_id = Column(
4991 4997 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4992 4998 nullable=True, unique=None, default=None)
4993 4999 repo_group = relationship('RepoGroup', lazy='joined')
4994 5000
4995 5001 @property
4996 5002 def scope(self):
4997 5003 if self.repo:
4998 5004 return repr(self.repo)
4999 5005 if self.repo_group:
5000 5006 if self.child_repos_only:
5001 5007 return repr(self.repo_group) + ' (child repos only)'
5002 5008 else:
5003 5009 return repr(self.repo_group) + ' (recursive)'
5004 5010 if self.child_repos_only:
5005 5011 return 'root_repos'
5006 5012 return 'global'
5007 5013
5008 5014 def __repr__(self):
5009 5015 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5010 5016
5011 5017
5012 5018 class RepoReviewRuleUser(Base, BaseModel):
5013 5019 __tablename__ = 'repo_review_rules_users'
5014 5020 __table_args__ = (
5015 5021 base_table_args
5016 5022 )
5017 5023 ROLE_REVIEWER = u'reviewer'
5018 5024 ROLE_OBSERVER = u'observer'
5019 5025 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5020 5026
5021 5027 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5022 5028 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5023 5029 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5024 5030 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5025 5031 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5026 5032 user = relationship('User')
5027 5033
5028 5034 def rule_data(self):
5029 5035 return {
5030 5036 'mandatory': self.mandatory,
5031 5037 'role': self.role,
5032 5038 }
5033 5039
5034 5040
5035 5041 class RepoReviewRuleUserGroup(Base, BaseModel):
5036 5042 __tablename__ = 'repo_review_rules_users_groups'
5037 5043 __table_args__ = (
5038 5044 base_table_args
5039 5045 )
5040 5046
5041 5047 VOTE_RULE_ALL = -1
5042 5048 ROLE_REVIEWER = u'reviewer'
5043 5049 ROLE_OBSERVER = u'observer'
5044 5050 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5045 5051
5046 5052 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5047 5053 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5048 5054 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5049 5055 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5050 5056 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5051 5057 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5052 5058 users_group = relationship('UserGroup')
5053 5059
5054 5060 def rule_data(self):
5055 5061 return {
5056 5062 'mandatory': self.mandatory,
5057 5063 'role': self.role,
5058 5064 'vote_rule': self.vote_rule
5059 5065 }
5060 5066
5061 5067 @property
5062 5068 def vote_rule_label(self):
5063 5069 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5064 5070 return 'all must vote'
5065 5071 else:
5066 5072 return 'min. vote {}'.format(self.vote_rule)
5067 5073
5068 5074
5069 5075 class RepoReviewRule(Base, BaseModel):
5070 5076 __tablename__ = 'repo_review_rules'
5071 5077 __table_args__ = (
5072 5078 base_table_args
5073 5079 )
5074 5080
5075 5081 repo_review_rule_id = Column(
5076 5082 'repo_review_rule_id', Integer(), primary_key=True)
5077 5083 repo_id = Column(
5078 5084 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5079 5085 repo = relationship('Repository', backref='review_rules')
5080 5086
5081 5087 review_rule_name = Column('review_rule_name', String(255))
5082 5088 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5083 5089 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5084 5090 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5085 5091
5086 5092 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5087 5093
5088 5094 # Legacy fields, just for backward compat
5089 5095 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5090 5096 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5091 5097
5092 5098 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5093 5099 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5094 5100
5095 5101 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5096 5102
5097 5103 rule_users = relationship('RepoReviewRuleUser')
5098 5104 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5099 5105
5100 5106 def _validate_pattern(self, value):
5101 5107 re.compile('^' + glob2re(value) + '$')
5102 5108
5103 5109 @hybrid_property
5104 5110 def source_branch_pattern(self):
5105 5111 return self._branch_pattern or '*'
5106 5112
5107 5113 @source_branch_pattern.setter
5108 5114 def source_branch_pattern(self, value):
5109 5115 self._validate_pattern(value)
5110 5116 self._branch_pattern = value or '*'
5111 5117
5112 5118 @hybrid_property
5113 5119 def target_branch_pattern(self):
5114 5120 return self._target_branch_pattern or '*'
5115 5121
5116 5122 @target_branch_pattern.setter
5117 5123 def target_branch_pattern(self, value):
5118 5124 self._validate_pattern(value)
5119 5125 self._target_branch_pattern = value or '*'
5120 5126
5121 5127 @hybrid_property
5122 5128 def file_pattern(self):
5123 5129 return self._file_pattern or '*'
5124 5130
5125 5131 @file_pattern.setter
5126 5132 def file_pattern(self, value):
5127 5133 self._validate_pattern(value)
5128 5134 self._file_pattern = value or '*'
5129 5135
5130 5136 @hybrid_property
5131 5137 def forbid_pr_author_to_review(self):
5132 5138 return self.pr_author == 'forbid_pr_author'
5133 5139
5134 5140 @hybrid_property
5135 5141 def include_pr_author_to_review(self):
5136 5142 return self.pr_author == 'include_pr_author'
5137 5143
5138 5144 @hybrid_property
5139 5145 def forbid_commit_author_to_review(self):
5140 5146 return self.commit_author == 'forbid_commit_author'
5141 5147
5142 5148 @hybrid_property
5143 5149 def include_commit_author_to_review(self):
5144 5150 return self.commit_author == 'include_commit_author'
5145 5151
5146 5152 def matches(self, source_branch, target_branch, files_changed):
5147 5153 """
5148 5154 Check if this review rule matches a branch/files in a pull request
5149 5155
5150 5156 :param source_branch: source branch name for the commit
5151 5157 :param target_branch: target branch name for the commit
5152 5158 :param files_changed: list of file paths changed in the pull request
5153 5159 """
5154 5160
5155 5161 source_branch = source_branch or ''
5156 5162 target_branch = target_branch or ''
5157 5163 files_changed = files_changed or []
5158 5164
5159 5165 branch_matches = True
5160 5166 if source_branch or target_branch:
5161 5167 if self.source_branch_pattern == '*':
5162 5168 source_branch_match = True
5163 5169 else:
5164 5170 if self.source_branch_pattern.startswith('re:'):
5165 5171 source_pattern = self.source_branch_pattern[3:]
5166 5172 else:
5167 5173 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5168 5174 source_branch_regex = re.compile(source_pattern)
5169 5175 source_branch_match = bool(source_branch_regex.search(source_branch))
5170 5176 if self.target_branch_pattern == '*':
5171 5177 target_branch_match = True
5172 5178 else:
5173 5179 if self.target_branch_pattern.startswith('re:'):
5174 5180 target_pattern = self.target_branch_pattern[3:]
5175 5181 else:
5176 5182 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5177 5183 target_branch_regex = re.compile(target_pattern)
5178 5184 target_branch_match = bool(target_branch_regex.search(target_branch))
5179 5185
5180 5186 branch_matches = source_branch_match and target_branch_match
5181 5187
5182 5188 files_matches = True
5183 5189 if self.file_pattern != '*':
5184 5190 files_matches = False
5185 5191 if self.file_pattern.startswith('re:'):
5186 5192 file_pattern = self.file_pattern[3:]
5187 5193 else:
5188 5194 file_pattern = glob2re(self.file_pattern)
5189 5195 file_regex = re.compile(file_pattern)
5190 5196 for file_data in files_changed:
5191 5197 filename = file_data.get('filename')
5192 5198
5193 5199 if file_regex.search(filename):
5194 5200 files_matches = True
5195 5201 break
5196 5202
5197 5203 return branch_matches and files_matches
5198 5204
5199 5205 @property
5200 5206 def review_users(self):
5201 5207 """ Returns the users which this rule applies to """
5202 5208
5203 5209 users = collections.OrderedDict()
5204 5210
5205 5211 for rule_user in self.rule_users:
5206 5212 if rule_user.user.active:
5207 5213 if rule_user.user not in users:
5208 5214 users[rule_user.user.username] = {
5209 5215 'user': rule_user.user,
5210 5216 'source': 'user',
5211 5217 'source_data': {},
5212 5218 'data': rule_user.rule_data()
5213 5219 }
5214 5220
5215 5221 for rule_user_group in self.rule_user_groups:
5216 5222 source_data = {
5217 5223 'user_group_id': rule_user_group.users_group.users_group_id,
5218 5224 'name': rule_user_group.users_group.users_group_name,
5219 5225 'members': len(rule_user_group.users_group.members)
5220 5226 }
5221 5227 for member in rule_user_group.users_group.members:
5222 5228 if member.user.active:
5223 5229 key = member.user.username
5224 5230 if key in users:
5225 5231 # skip this member as we have him already
5226 5232 # this prevents from override the "first" matched
5227 5233 # users with duplicates in multiple groups
5228 5234 continue
5229 5235
5230 5236 users[key] = {
5231 5237 'user': member.user,
5232 5238 'source': 'user_group',
5233 5239 'source_data': source_data,
5234 5240 'data': rule_user_group.rule_data()
5235 5241 }
5236 5242
5237 5243 return users
5238 5244
5239 5245 def user_group_vote_rule(self, user_id):
5240 5246
5241 5247 rules = []
5242 5248 if not self.rule_user_groups:
5243 5249 return rules
5244 5250
5245 5251 for user_group in self.rule_user_groups:
5246 5252 user_group_members = [x.user_id for x in user_group.users_group.members]
5247 5253 if user_id in user_group_members:
5248 5254 rules.append(user_group)
5249 5255 return rules
5250 5256
5251 5257 def __repr__(self):
5252 5258 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5253 5259 self.repo_review_rule_id, self.repo)
5254 5260
5255 5261
5256 5262 class ScheduleEntry(Base, BaseModel):
5257 5263 __tablename__ = 'schedule_entries'
5258 5264 __table_args__ = (
5259 5265 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5260 5266 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5261 5267 base_table_args,
5262 5268 )
5263 5269
5264 5270 schedule_types = ['crontab', 'timedelta', 'integer']
5265 5271 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5266 5272
5267 5273 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5268 5274 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5269 5275 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5270 5276
5271 5277 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5272 5278 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5273 5279
5274 5280 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5275 5281 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5276 5282
5277 5283 # task
5278 5284 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5279 5285 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5280 5286 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5281 5287 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5282 5288
5283 5289 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5284 5290 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5285 5291
5286 5292 @hybrid_property
5287 5293 def schedule_type(self):
5288 5294 return self._schedule_type
5289 5295
5290 5296 @schedule_type.setter
5291 5297 def schedule_type(self, val):
5292 5298 if val not in self.schedule_types:
5293 5299 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5294 5300 val, self.schedule_type))
5295 5301
5296 5302 self._schedule_type = val
5297 5303
5298 5304 @classmethod
5299 5305 def get_uid(cls, obj):
5300 5306 args = obj.task_args
5301 5307 kwargs = obj.task_kwargs
5302 5308 if isinstance(args, JsonRaw):
5303 5309 try:
5304 5310 args = json.loads(args)
5305 5311 except ValueError:
5306 5312 args = tuple()
5307 5313
5308 5314 if isinstance(kwargs, JsonRaw):
5309 5315 try:
5310 5316 kwargs = json.loads(kwargs)
5311 5317 except ValueError:
5312 5318 kwargs = dict()
5313 5319
5314 5320 dot_notation = obj.task_dot_notation
5315 5321 val = '.'.join(map(safe_str, [
5316 5322 sorted(dot_notation), args, sorted(kwargs.items())]))
5317 5323 return hashlib.sha1(val).hexdigest()
5318 5324
5319 5325 @classmethod
5320 5326 def get_by_schedule_name(cls, schedule_name):
5321 5327 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5322 5328
5323 5329 @classmethod
5324 5330 def get_by_schedule_id(cls, schedule_id):
5325 5331 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5326 5332
5327 5333 @property
5328 5334 def task(self):
5329 5335 return self.task_dot_notation
5330 5336
5331 5337 @property
5332 5338 def schedule(self):
5333 5339 from rhodecode.lib.celerylib.utils import raw_2_schedule
5334 5340 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5335 5341 return schedule
5336 5342
5337 5343 @property
5338 5344 def args(self):
5339 5345 try:
5340 5346 return list(self.task_args or [])
5341 5347 except ValueError:
5342 5348 return list()
5343 5349
5344 5350 @property
5345 5351 def kwargs(self):
5346 5352 try:
5347 5353 return dict(self.task_kwargs or {})
5348 5354 except ValueError:
5349 5355 return dict()
5350 5356
5351 5357 def _as_raw(self, val, indent=None):
5352 5358 if hasattr(val, 'de_coerce'):
5353 5359 val = val.de_coerce()
5354 5360 if val:
5355 5361 val = json.dumps(val, indent=indent, sort_keys=True)
5356 5362
5357 5363 return val
5358 5364
5359 5365 @property
5360 5366 def schedule_definition_raw(self):
5361 5367 return self._as_raw(self.schedule_definition)
5362 5368
5363 5369 def args_raw(self, indent=None):
5364 5370 return self._as_raw(self.task_args, indent)
5365 5371
5366 5372 def kwargs_raw(self, indent=None):
5367 5373 return self._as_raw(self.task_kwargs, indent)
5368 5374
5369 5375 def __repr__(self):
5370 5376 return '<DB:ScheduleEntry({}:{})>'.format(
5371 5377 self.schedule_entry_id, self.schedule_name)
5372 5378
5373 5379
5374 5380 @event.listens_for(ScheduleEntry, 'before_update')
5375 5381 def update_task_uid(mapper, connection, target):
5376 5382 target.task_uid = ScheduleEntry.get_uid(target)
5377 5383
5378 5384
5379 5385 @event.listens_for(ScheduleEntry, 'before_insert')
5380 5386 def set_task_uid(mapper, connection, target):
5381 5387 target.task_uid = ScheduleEntry.get_uid(target)
5382 5388
5383 5389
5384 5390 class _BaseBranchPerms(BaseModel):
5385 5391 @classmethod
5386 5392 def compute_hash(cls, value):
5387 5393 return sha1_safe(value)
5388 5394
5389 5395 @hybrid_property
5390 5396 def branch_pattern(self):
5391 5397 return self._branch_pattern or '*'
5392 5398
5393 5399 @hybrid_property
5394 5400 def branch_hash(self):
5395 5401 return self._branch_hash
5396 5402
5397 5403 def _validate_glob(self, value):
5398 5404 re.compile('^' + glob2re(value) + '$')
5399 5405
5400 5406 @branch_pattern.setter
5401 5407 def branch_pattern(self, value):
5402 5408 self._validate_glob(value)
5403 5409 self._branch_pattern = value or '*'
5404 5410 # set the Hash when setting the branch pattern
5405 5411 self._branch_hash = self.compute_hash(self._branch_pattern)
5406 5412
5407 5413 def matches(self, branch):
5408 5414 """
5409 5415 Check if this the branch matches entry
5410 5416
5411 5417 :param branch: branch name for the commit
5412 5418 """
5413 5419
5414 5420 branch = branch or ''
5415 5421
5416 5422 branch_matches = True
5417 5423 if branch:
5418 5424 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5419 5425 branch_matches = bool(branch_regex.search(branch))
5420 5426
5421 5427 return branch_matches
5422 5428
5423 5429
5424 5430 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5425 5431 __tablename__ = 'user_to_repo_branch_permissions'
5426 5432 __table_args__ = (
5427 5433 base_table_args
5428 5434 )
5429 5435
5430 5436 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5431 5437
5432 5438 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5433 5439 repo = relationship('Repository', backref='user_branch_perms')
5434 5440
5435 5441 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5436 5442 permission = relationship('Permission')
5437 5443
5438 5444 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5439 5445 user_repo_to_perm = relationship('UserRepoToPerm')
5440 5446
5441 5447 rule_order = Column('rule_order', Integer(), nullable=False)
5442 5448 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5443 5449 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5444 5450
5445 5451 def __unicode__(self):
5446 5452 return u'<UserBranchPermission(%s => %r)>' % (
5447 5453 self.user_repo_to_perm, self.branch_pattern)
5448 5454
5449 5455
5450 5456 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5451 5457 __tablename__ = 'user_group_to_repo_branch_permissions'
5452 5458 __table_args__ = (
5453 5459 base_table_args
5454 5460 )
5455 5461
5456 5462 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5457 5463
5458 5464 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5459 5465 repo = relationship('Repository', backref='user_group_branch_perms')
5460 5466
5461 5467 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5462 5468 permission = relationship('Permission')
5463 5469
5464 5470 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5465 5471 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5466 5472
5467 5473 rule_order = Column('rule_order', Integer(), nullable=False)
5468 5474 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5469 5475 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5470 5476
5471 5477 def __unicode__(self):
5472 5478 return u'<UserBranchPermission(%s => %r)>' % (
5473 5479 self.user_group_repo_to_perm, self.branch_pattern)
5474 5480
5475 5481
5476 5482 class UserBookmark(Base, BaseModel):
5477 5483 __tablename__ = 'user_bookmarks'
5478 5484 __table_args__ = (
5479 5485 UniqueConstraint('user_id', 'bookmark_repo_id'),
5480 5486 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5481 5487 UniqueConstraint('user_id', 'bookmark_position'),
5482 5488 base_table_args
5483 5489 )
5484 5490
5485 5491 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5486 5492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5487 5493 position = Column("bookmark_position", Integer(), nullable=False)
5488 5494 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5489 5495 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5490 5496 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5491 5497
5492 5498 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5493 5499 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5494 5500
5495 5501 user = relationship("User")
5496 5502
5497 5503 repository = relationship("Repository")
5498 5504 repository_group = relationship("RepoGroup")
5499 5505
5500 5506 @classmethod
5501 5507 def get_by_position_for_user(cls, position, user_id):
5502 5508 return cls.query() \
5503 5509 .filter(UserBookmark.user_id == user_id) \
5504 5510 .filter(UserBookmark.position == position).scalar()
5505 5511
5506 5512 @classmethod
5507 5513 def get_bookmarks_for_user(cls, user_id, cache=True):
5508 5514 bookmarks = cls.query() \
5509 5515 .filter(UserBookmark.user_id == user_id) \
5510 5516 .options(joinedload(UserBookmark.repository)) \
5511 5517 .options(joinedload(UserBookmark.repository_group)) \
5512 5518 .order_by(UserBookmark.position.asc())
5513 5519
5514 5520 if cache:
5515 5521 bookmarks = bookmarks.options(
5516 5522 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5517 5523 )
5518 5524
5519 5525 return bookmarks.all()
5520 5526
5521 5527 def __unicode__(self):
5522 5528 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5523 5529
5524 5530
5525 5531 class FileStore(Base, BaseModel):
5526 5532 __tablename__ = 'file_store'
5527 5533 __table_args__ = (
5528 5534 base_table_args
5529 5535 )
5530 5536
5531 5537 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5532 5538 file_uid = Column('file_uid', String(1024), nullable=False)
5533 5539 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5534 5540 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5535 5541 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5536 5542
5537 5543 # sha256 hash
5538 5544 file_hash = Column('file_hash', String(512), nullable=False)
5539 5545 file_size = Column('file_size', BigInteger(), nullable=False)
5540 5546
5541 5547 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5542 5548 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5543 5549 accessed_count = Column('accessed_count', Integer(), default=0)
5544 5550
5545 5551 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5546 5552
5547 5553 # if repo/repo_group reference is set, check for permissions
5548 5554 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5549 5555
5550 5556 # hidden defines an attachment that should be hidden from showing in artifact listing
5551 5557 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5552 5558
5553 5559 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5554 5560 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5555 5561
5556 5562 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5557 5563
5558 5564 # scope limited to user, which requester have access to
5559 5565 scope_user_id = Column(
5560 5566 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5561 5567 nullable=True, unique=None, default=None)
5562 5568 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5563 5569
5564 5570 # scope limited to user group, which requester have access to
5565 5571 scope_user_group_id = Column(
5566 5572 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5567 5573 nullable=True, unique=None, default=None)
5568 5574 user_group = relationship('UserGroup', lazy='joined')
5569 5575
5570 5576 # scope limited to repo, which requester have access to
5571 5577 scope_repo_id = Column(
5572 5578 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5573 5579 nullable=True, unique=None, default=None)
5574 5580 repo = relationship('Repository', lazy='joined')
5575 5581
5576 5582 # scope limited to repo group, which requester have access to
5577 5583 scope_repo_group_id = Column(
5578 5584 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5579 5585 nullable=True, unique=None, default=None)
5580 5586 repo_group = relationship('RepoGroup', lazy='joined')
5581 5587
5582 5588 @classmethod
5583 5589 def get_by_store_uid(cls, file_store_uid, safe=False):
5584 5590 if safe:
5585 5591 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5586 5592 else:
5587 5593 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5588 5594
5589 5595 @classmethod
5590 5596 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5591 5597 file_description='', enabled=True, hidden=False, check_acl=True,
5592 5598 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5593 5599
5594 5600 store_entry = FileStore()
5595 5601 store_entry.file_uid = file_uid
5596 5602 store_entry.file_display_name = file_display_name
5597 5603 store_entry.file_org_name = filename
5598 5604 store_entry.file_size = file_size
5599 5605 store_entry.file_hash = file_hash
5600 5606 store_entry.file_description = file_description
5601 5607
5602 5608 store_entry.check_acl = check_acl
5603 5609 store_entry.enabled = enabled
5604 5610 store_entry.hidden = hidden
5605 5611
5606 5612 store_entry.user_id = user_id
5607 5613 store_entry.scope_user_id = scope_user_id
5608 5614 store_entry.scope_repo_id = scope_repo_id
5609 5615 store_entry.scope_repo_group_id = scope_repo_group_id
5610 5616
5611 5617 return store_entry
5612 5618
5613 5619 @classmethod
5614 5620 def store_metadata(cls, file_store_id, args, commit=True):
5615 5621 file_store = FileStore.get(file_store_id)
5616 5622 if file_store is None:
5617 5623 return
5618 5624
5619 5625 for section, key, value, value_type in args:
5620 5626 has_key = FileStoreMetadata().query() \
5621 5627 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5622 5628 .filter(FileStoreMetadata.file_store_meta_section == section) \
5623 5629 .filter(FileStoreMetadata.file_store_meta_key == key) \
5624 5630 .scalar()
5625 5631 if has_key:
5626 5632 msg = 'key `{}` already defined under section `{}` for this file.'\
5627 5633 .format(key, section)
5628 5634 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5629 5635
5630 5636 # NOTE(marcink): raises ArtifactMetadataBadValueType
5631 5637 FileStoreMetadata.valid_value_type(value_type)
5632 5638
5633 5639 meta_entry = FileStoreMetadata()
5634 5640 meta_entry.file_store = file_store
5635 5641 meta_entry.file_store_meta_section = section
5636 5642 meta_entry.file_store_meta_key = key
5637 5643 meta_entry.file_store_meta_value_type = value_type
5638 5644 meta_entry.file_store_meta_value = value
5639 5645
5640 5646 Session().add(meta_entry)
5641 5647
5642 5648 try:
5643 5649 if commit:
5644 5650 Session().commit()
5645 5651 except IntegrityError:
5646 5652 Session().rollback()
5647 5653 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5648 5654
5649 5655 @classmethod
5650 5656 def bump_access_counter(cls, file_uid, commit=True):
5651 5657 FileStore().query()\
5652 5658 .filter(FileStore.file_uid == file_uid)\
5653 5659 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5654 5660 FileStore.accessed_on: datetime.datetime.now()})
5655 5661 if commit:
5656 5662 Session().commit()
5657 5663
5658 5664 def __json__(self):
5659 5665 data = {
5660 5666 'filename': self.file_display_name,
5661 5667 'filename_org': self.file_org_name,
5662 5668 'file_uid': self.file_uid,
5663 5669 'description': self.file_description,
5664 5670 'hidden': self.hidden,
5665 5671 'size': self.file_size,
5666 5672 'created_on': self.created_on,
5667 5673 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5668 5674 'downloaded_times': self.accessed_count,
5669 5675 'sha256': self.file_hash,
5670 5676 'metadata': self.file_metadata,
5671 5677 }
5672 5678
5673 5679 return data
5674 5680
5675 5681 def __repr__(self):
5676 5682 return '<FileStore({})>'.format(self.file_store_id)
5677 5683
5678 5684
5679 5685 class FileStoreMetadata(Base, BaseModel):
5680 5686 __tablename__ = 'file_store_metadata'
5681 5687 __table_args__ = (
5682 5688 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5683 5689 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5684 5690 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5685 5691 base_table_args
5686 5692 )
5687 5693 SETTINGS_TYPES = {
5688 5694 'str': safe_str,
5689 5695 'int': safe_int,
5690 5696 'unicode': safe_unicode,
5691 5697 'bool': str2bool,
5692 5698 'list': functools.partial(aslist, sep=',')
5693 5699 }
5694 5700
5695 5701 file_store_meta_id = Column(
5696 5702 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5697 5703 primary_key=True)
5698 5704 _file_store_meta_section = Column(
5699 5705 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5700 5706 nullable=True, unique=None, default=None)
5701 5707 _file_store_meta_section_hash = Column(
5702 5708 "file_store_meta_section_hash", String(255),
5703 5709 nullable=True, unique=None, default=None)
5704 5710 _file_store_meta_key = Column(
5705 5711 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5706 5712 nullable=True, unique=None, default=None)
5707 5713 _file_store_meta_key_hash = Column(
5708 5714 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5709 5715 _file_store_meta_value = Column(
5710 5716 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5711 5717 nullable=True, unique=None, default=None)
5712 5718 _file_store_meta_value_type = Column(
5713 5719 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5714 5720 default='unicode')
5715 5721
5716 5722 file_store_id = Column(
5717 5723 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5718 5724 nullable=True, unique=None, default=None)
5719 5725
5720 5726 file_store = relationship('FileStore', lazy='joined')
5721 5727
5722 5728 @classmethod
5723 5729 def valid_value_type(cls, value):
5724 5730 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5725 5731 raise ArtifactMetadataBadValueType(
5726 5732 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5727 5733
5728 5734 @hybrid_property
5729 5735 def file_store_meta_section(self):
5730 5736 return self._file_store_meta_section
5731 5737
5732 5738 @file_store_meta_section.setter
5733 5739 def file_store_meta_section(self, value):
5734 5740 self._file_store_meta_section = value
5735 5741 self._file_store_meta_section_hash = _hash_key(value)
5736 5742
5737 5743 @hybrid_property
5738 5744 def file_store_meta_key(self):
5739 5745 return self._file_store_meta_key
5740 5746
5741 5747 @file_store_meta_key.setter
5742 5748 def file_store_meta_key(self, value):
5743 5749 self._file_store_meta_key = value
5744 5750 self._file_store_meta_key_hash = _hash_key(value)
5745 5751
5746 5752 @hybrid_property
5747 5753 def file_store_meta_value(self):
5748 5754 val = self._file_store_meta_value
5749 5755
5750 5756 if self._file_store_meta_value_type:
5751 5757 # e.g unicode.encrypted == unicode
5752 5758 _type = self._file_store_meta_value_type.split('.')[0]
5753 5759 # decode the encrypted value if it's encrypted field type
5754 5760 if '.encrypted' in self._file_store_meta_value_type:
5755 5761 cipher = EncryptedTextValue()
5756 5762 val = safe_unicode(cipher.process_result_value(val, None))
5757 5763 # do final type conversion
5758 5764 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5759 5765 val = converter(val)
5760 5766
5761 5767 return val
5762 5768
5763 5769 @file_store_meta_value.setter
5764 5770 def file_store_meta_value(self, val):
5765 5771 val = safe_unicode(val)
5766 5772 # encode the encrypted value
5767 5773 if '.encrypted' in self.file_store_meta_value_type:
5768 5774 cipher = EncryptedTextValue()
5769 5775 val = safe_unicode(cipher.process_bind_param(val, None))
5770 5776 self._file_store_meta_value = val
5771 5777
5772 5778 @hybrid_property
5773 5779 def file_store_meta_value_type(self):
5774 5780 return self._file_store_meta_value_type
5775 5781
5776 5782 @file_store_meta_value_type.setter
5777 5783 def file_store_meta_value_type(self, val):
5778 5784 # e.g unicode.encrypted
5779 5785 self.valid_value_type(val)
5780 5786 self._file_store_meta_value_type = val
5781 5787
5782 5788 def __json__(self):
5783 5789 data = {
5784 5790 'artifact': self.file_store.file_uid,
5785 5791 'section': self.file_store_meta_section,
5786 5792 'key': self.file_store_meta_key,
5787 5793 'value': self.file_store_meta_value,
5788 5794 }
5789 5795
5790 5796 return data
5791 5797
5792 5798 def __repr__(self):
5793 5799 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5794 5800 self.file_store_meta_key, self.file_store_meta_value)
5795 5801
5796 5802
5797 5803 class DbMigrateVersion(Base, BaseModel):
5798 5804 __tablename__ = 'db_migrate_version'
5799 5805 __table_args__ = (
5800 5806 base_table_args,
5801 5807 )
5802 5808
5803 5809 repository_id = Column('repository_id', String(250), primary_key=True)
5804 5810 repository_path = Column('repository_path', Text)
5805 5811 version = Column('version', Integer)
5806 5812
5807 5813 @classmethod
5808 5814 def set_version(cls, version):
5809 5815 """
5810 5816 Helper for forcing a different version, usually for debugging purposes via ishell.
5811 5817 """
5812 5818 ver = DbMigrateVersion.query().first()
5813 5819 ver.version = version
5814 5820 Session().commit()
5815 5821
5816 5822
5817 5823 class DbSession(Base, BaseModel):
5818 5824 __tablename__ = 'db_session'
5819 5825 __table_args__ = (
5820 5826 base_table_args,
5821 5827 )
5822 5828
5823 5829 def __repr__(self):
5824 5830 return '<DB:DbSession({})>'.format(self.id)
5825 5831
5826 5832 id = Column('id', Integer())
5827 5833 namespace = Column('namespace', String(255), primary_key=True)
5828 5834 accessed = Column('accessed', DateTime, nullable=False)
5829 5835 created = Column('created', DateTime, nullable=False)
5830 5836 data = Column('data', PickleType, nullable=False)
@@ -1,640 +1,641 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 this is forms validation classes
23 23 http://formencode.org/module-formencode.validators.html
24 24 for list off all availible validators
25 25
26 26 we can create our own validators
27 27
28 28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 29 pre_validators [] These validators will be applied before the schema
30 30 chained_validators [] These validators will be applied after the schema
31 31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35 35
36 36
37 37 <name> = formencode.validators.<name of validator>
38 38 <name> must equal form name
39 39 list=[1,2,3,4,5]
40 40 for SELECT use formencode.All(OneOf(list), Int())
41 41
42 42 """
43 43
44 44 import deform
45 45 import logging
46 46 import formencode
47 47
48 48 from pkg_resources import resource_filename
49 49 from formencode import All, Pipe
50 50
51 51 from pyramid.threadlocal import get_current_request
52 52
53 53 from rhodecode import BACKENDS
54 54 from rhodecode.lib import helpers
55 55 from rhodecode.model import validators as v
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 deform_templates = resource_filename('deform', 'templates')
61 61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 62 search_path = (rhodecode_templates, deform_templates)
63 63
64 64
65 65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 67 def __call__(self, template_name, **kw):
68 68 kw['h'] = helpers
69 69 kw['request'] = get_current_request()
70 70 return self.load(template_name)(**kw)
71 71
72 72
73 73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
74 74 deform.Form.set_default_renderer(form_renderer)
75 75
76 76
77 77 def LoginForm(localizer):
78 78 _ = localizer
79 79
80 80 class _LoginForm(formencode.Schema):
81 81 allow_extra_fields = True
82 82 filter_extra_fields = True
83 83 username = v.UnicodeString(
84 84 strip=True,
85 85 min=1,
86 86 not_empty=True,
87 87 messages={
88 88 'empty': _(u'Please enter a login'),
89 89 'tooShort': _(u'Enter a value %(min)i characters long or more')
90 90 }
91 91 )
92 92
93 93 password = v.UnicodeString(
94 94 strip=False,
95 95 min=3,
96 96 max=72,
97 97 not_empty=True,
98 98 messages={
99 99 'empty': _(u'Please enter a password'),
100 100 'tooShort': _(u'Enter %(min)i characters or more')}
101 101 )
102 102
103 103 remember = v.StringBoolean(if_missing=False)
104 104
105 105 chained_validators = [v.ValidAuth(localizer)]
106 106 return _LoginForm
107 107
108 108
109 109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
110 110 old_data = old_data or {}
111 111 available_languages = available_languages or []
112 112 _ = localizer
113 113
114 114 class _UserForm(formencode.Schema):
115 115 allow_extra_fields = True
116 116 filter_extra_fields = True
117 117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
118 118 v.ValidUsername(localizer, edit, old_data))
119 119 if edit:
120 120 new_password = All(
121 121 v.ValidPassword(localizer),
122 122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
123 123 )
124 124 password_confirmation = All(
125 125 v.ValidPassword(localizer),
126 126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
127 127 )
128 128 admin = v.StringBoolean(if_missing=False)
129 129 else:
130 130 password = All(
131 131 v.ValidPassword(localizer),
132 132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
133 133 )
134 134 password_confirmation = All(
135 135 v.ValidPassword(localizer),
136 136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
137 137 )
138 138
139 139 password_change = v.StringBoolean(if_missing=False)
140 140 create_repo_group = v.StringBoolean(if_missing=False)
141 141
142 142 active = v.StringBoolean(if_missing=False)
143 143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
145 145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
146 146 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
147 147 if_missing='')
148 148 extern_name = v.UnicodeString(strip=True)
149 149 extern_type = v.UnicodeString(strip=True)
150 150 language = v.OneOf(available_languages, hideList=False,
151 151 testValueList=True, if_missing=None)
152 152 chained_validators = [v.ValidPasswordsMatch(localizer)]
153 153 return _UserForm
154 154
155 155
156 156 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
157 157 old_data = old_data or {}
158 158 _ = localizer
159 159
160 160 class _UserGroupForm(formencode.Schema):
161 161 allow_extra_fields = True
162 162 filter_extra_fields = True
163 163
164 164 users_group_name = All(
165 165 v.UnicodeString(strip=True, min=1, not_empty=True),
166 166 v.ValidUserGroup(localizer, edit, old_data)
167 167 )
168 168 user_group_description = v.UnicodeString(strip=True, min=1,
169 169 not_empty=False)
170 170
171 171 users_group_active = v.StringBoolean(if_missing=False)
172 172
173 173 if edit:
174 174 # this is user group owner
175 175 user = All(
176 176 v.UnicodeString(not_empty=True),
177 177 v.ValidRepoUser(localizer, allow_disabled))
178 178 return _UserGroupForm
179 179
180 180
181 181 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
182 182 can_create_in_root=False, allow_disabled=False):
183 183 _ = localizer
184 184 old_data = old_data or {}
185 185 available_groups = available_groups or []
186 186
187 187 class _RepoGroupForm(formencode.Schema):
188 188 allow_extra_fields = True
189 189 filter_extra_fields = False
190 190
191 191 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
192 192 v.SlugifyName(localizer),)
193 193 group_description = v.UnicodeString(strip=True, min=1,
194 194 not_empty=False)
195 195 group_copy_permissions = v.StringBoolean(if_missing=False)
196 196
197 197 group_parent_id = v.OneOf(available_groups, hideList=False,
198 198 testValueList=True, not_empty=True)
199 199 enable_locking = v.StringBoolean(if_missing=False)
200 200 chained_validators = [
201 201 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
202 202
203 203 if edit:
204 204 # this is repo group owner
205 205 user = All(
206 206 v.UnicodeString(not_empty=True),
207 207 v.ValidRepoUser(localizer, allow_disabled))
208 208 return _RepoGroupForm
209 209
210 210
211 211 def RegisterForm(localizer, edit=False, old_data=None):
212 212 _ = localizer
213 213 old_data = old_data or {}
214 214
215 215 class _RegisterForm(formencode.Schema):
216 216 allow_extra_fields = True
217 217 filter_extra_fields = True
218 218 username = All(
219 219 v.ValidUsername(localizer, edit, old_data),
220 220 v.UnicodeString(strip=True, min=1, not_empty=True)
221 221 )
222 222 password = All(
223 223 v.ValidPassword(localizer),
224 224 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
225 225 )
226 226 password_confirmation = All(
227 227 v.ValidPassword(localizer),
228 228 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
229 229 )
230 230 active = v.StringBoolean(if_missing=False)
231 231 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
232 232 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
233 233 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
234 234
235 235 chained_validators = [v.ValidPasswordsMatch(localizer)]
236 236 return _RegisterForm
237 237
238 238
239 239 def PasswordResetForm(localizer):
240 240 _ = localizer
241 241
242 242 class _PasswordResetForm(formencode.Schema):
243 243 allow_extra_fields = True
244 244 filter_extra_fields = True
245 245 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
246 246 return _PasswordResetForm
247 247
248 248
249 249 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
250 250 _ = localizer
251 251 old_data = old_data or {}
252 252 repo_groups = repo_groups or []
253 253 supported_backends = BACKENDS.keys()
254 254
255 255 class _RepoForm(formencode.Schema):
256 256 allow_extra_fields = True
257 257 filter_extra_fields = False
258 258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
259 259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
260 260 repo_group = All(v.CanWriteGroup(localizer, old_data),
261 261 v.OneOf(repo_groups, hideList=True))
262 262 repo_type = v.OneOf(supported_backends, required=False,
263 263 if_missing=old_data.get('repo_type'))
264 264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
265 265 repo_private = v.StringBoolean(if_missing=False)
266 266 repo_copy_permissions = v.StringBoolean(if_missing=False)
267 267 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
268 268
269 269 repo_enable_statistics = v.StringBoolean(if_missing=False)
270 270 repo_enable_downloads = v.StringBoolean(if_missing=False)
271 271 repo_enable_locking = v.StringBoolean(if_missing=False)
272 272
273 273 if edit:
274 274 # this is repo owner
275 275 user = All(
276 276 v.UnicodeString(not_empty=True),
277 277 v.ValidRepoUser(localizer, allow_disabled))
278 278 clone_uri_change = v.UnicodeString(
279 279 not_empty=False, if_missing=v.Missing)
280 280
281 281 chained_validators = [v.ValidCloneUri(localizer),
282 282 v.ValidRepoName(localizer, edit, old_data)]
283 283 return _RepoForm
284 284
285 285
286 286 def RepoPermsForm(localizer):
287 287 _ = localizer
288 288
289 289 class _RepoPermsForm(formencode.Schema):
290 290 allow_extra_fields = True
291 291 filter_extra_fields = False
292 292 chained_validators = [v.ValidPerms(localizer, type_='repo')]
293 293 return _RepoPermsForm
294 294
295 295
296 296 def RepoGroupPermsForm(localizer, valid_recursive_choices):
297 297 _ = localizer
298 298
299 299 class _RepoGroupPermsForm(formencode.Schema):
300 300 allow_extra_fields = True
301 301 filter_extra_fields = False
302 302 recursive = v.OneOf(valid_recursive_choices)
303 303 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
304 304 return _RepoGroupPermsForm
305 305
306 306
307 307 def UserGroupPermsForm(localizer):
308 308 _ = localizer
309 309
310 310 class _UserPermsForm(formencode.Schema):
311 311 allow_extra_fields = True
312 312 filter_extra_fields = False
313 313 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
314 314 return _UserPermsForm
315 315
316 316
317 317 def RepoFieldForm(localizer):
318 318 _ = localizer
319 319
320 320 class _RepoFieldForm(formencode.Schema):
321 321 filter_extra_fields = True
322 322 allow_extra_fields = True
323 323
324 324 new_field_key = All(v.FieldKey(localizer),
325 325 v.UnicodeString(strip=True, min=3, not_empty=True))
326 326 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
327 327 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
328 328 if_missing='str')
329 329 new_field_label = v.UnicodeString(not_empty=False)
330 330 new_field_desc = v.UnicodeString(not_empty=False)
331 331 return _RepoFieldForm
332 332
333 333
334 334 def RepoForkForm(localizer, edit=False, old_data=None,
335 335 supported_backends=BACKENDS.keys(), repo_groups=None):
336 336 _ = localizer
337 337 old_data = old_data or {}
338 338 repo_groups = repo_groups or []
339 339
340 340 class _RepoForkForm(formencode.Schema):
341 341 allow_extra_fields = True
342 342 filter_extra_fields = False
343 343 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
344 344 v.SlugifyName(localizer))
345 345 repo_group = All(v.CanWriteGroup(localizer, ),
346 346 v.OneOf(repo_groups, hideList=True))
347 347 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
348 348 description = v.UnicodeString(strip=True, min=1, not_empty=True)
349 349 private = v.StringBoolean(if_missing=False)
350 350 copy_permissions = v.StringBoolean(if_missing=False)
351 351 fork_parent_id = v.UnicodeString()
352 352 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
353 353 return _RepoForkForm
354 354
355 355
356 356 def ApplicationSettingsForm(localizer):
357 357 _ = localizer
358 358
359 359 class _ApplicationSettingsForm(formencode.Schema):
360 360 allow_extra_fields = True
361 361 filter_extra_fields = False
362 362 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
363 363 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
364 364 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
365 365 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
366 366 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
367 367 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
368 368 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
369 369 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
370 370 return _ApplicationSettingsForm
371 371
372 372
373 373 def ApplicationVisualisationForm(localizer):
374 374 from rhodecode.model.db import Repository
375 375 _ = localizer
376 376
377 377 class _ApplicationVisualisationForm(formencode.Schema):
378 378 allow_extra_fields = True
379 379 filter_extra_fields = False
380 380 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
381 381 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
382 382 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
383 383
384 384 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
385 385 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
386 386 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
387 387 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
388 388 rhodecode_show_version = v.StringBoolean(if_missing=False)
389 389 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
390 390 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
391 391 rhodecode_gravatar_url = v.UnicodeString(min=3)
392 392 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
393 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
393 394 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
394 395 rhodecode_support_url = v.UnicodeString()
395 396 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
396 397 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
397 398 return _ApplicationVisualisationForm
398 399
399 400
400 401 class _BaseVcsSettingsForm(formencode.Schema):
401 402
402 403 allow_extra_fields = True
403 404 filter_extra_fields = False
404 405 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
405 406 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
406 407 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
407 408
408 409 # PR/Code-review
409 410 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
410 411 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
411 412
412 413 # hg
413 414 extensions_largefiles = v.StringBoolean(if_missing=False)
414 415 extensions_evolve = v.StringBoolean(if_missing=False)
415 416 phases_publish = v.StringBoolean(if_missing=False)
416 417
417 418 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
418 419 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
419 420
420 421 # git
421 422 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
422 423 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
423 424 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
424 425
425 426 # svn
426 427 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
427 428 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
428 429
429 430 # cache
430 431 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
431 432
432 433
433 434 def ApplicationUiSettingsForm(localizer):
434 435 _ = localizer
435 436
436 437 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
437 438 web_push_ssl = v.StringBoolean(if_missing=False)
438 439 paths_root_path = All(
439 440 v.ValidPath(localizer),
440 441 v.UnicodeString(strip=True, min=1, not_empty=True)
441 442 )
442 443 largefiles_usercache = All(
443 444 v.ValidPath(localizer),
444 445 v.UnicodeString(strip=True, min=2, not_empty=True))
445 446 vcs_git_lfs_store_location = All(
446 447 v.ValidPath(localizer),
447 448 v.UnicodeString(strip=True, min=2, not_empty=True))
448 449 extensions_hgsubversion = v.StringBoolean(if_missing=False)
449 450 extensions_hggit = v.StringBoolean(if_missing=False)
450 451 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
451 452 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
452 453 return _ApplicationUiSettingsForm
453 454
454 455
455 456 def RepoVcsSettingsForm(localizer, repo_name):
456 457 _ = localizer
457 458
458 459 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
459 460 inherit_global_settings = v.StringBoolean(if_missing=False)
460 461 new_svn_branch = v.ValidSvnPattern(localizer,
461 462 section='vcs_svn_branch', repo_name=repo_name)
462 463 new_svn_tag = v.ValidSvnPattern(localizer,
463 464 section='vcs_svn_tag', repo_name=repo_name)
464 465 return _RepoVcsSettingsForm
465 466
466 467
467 468 def LabsSettingsForm(localizer):
468 469 _ = localizer
469 470
470 471 class _LabSettingsForm(formencode.Schema):
471 472 allow_extra_fields = True
472 473 filter_extra_fields = False
473 474 return _LabSettingsForm
474 475
475 476
476 477 def ApplicationPermissionsForm(
477 478 localizer, register_choices, password_reset_choices,
478 479 extern_activate_choices):
479 480 _ = localizer
480 481
481 482 class _DefaultPermissionsForm(formencode.Schema):
482 483 allow_extra_fields = True
483 484 filter_extra_fields = True
484 485
485 486 anonymous = v.StringBoolean(if_missing=False)
486 487 default_register = v.OneOf(register_choices)
487 488 default_register_message = v.UnicodeString()
488 489 default_password_reset = v.OneOf(password_reset_choices)
489 490 default_extern_activate = v.OneOf(extern_activate_choices)
490 491 return _DefaultPermissionsForm
491 492
492 493
493 494 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
494 495 user_group_perms_choices):
495 496 _ = localizer
496 497
497 498 class _ObjectPermissionsForm(formencode.Schema):
498 499 allow_extra_fields = True
499 500 filter_extra_fields = True
500 501 overwrite_default_repo = v.StringBoolean(if_missing=False)
501 502 overwrite_default_group = v.StringBoolean(if_missing=False)
502 503 overwrite_default_user_group = v.StringBoolean(if_missing=False)
503 504
504 505 default_repo_perm = v.OneOf(repo_perms_choices)
505 506 default_group_perm = v.OneOf(group_perms_choices)
506 507 default_user_group_perm = v.OneOf(user_group_perms_choices)
507 508
508 509 return _ObjectPermissionsForm
509 510
510 511
511 512 def BranchPermissionsForm(localizer, branch_perms_choices):
512 513 _ = localizer
513 514
514 515 class _BranchPermissionsForm(formencode.Schema):
515 516 allow_extra_fields = True
516 517 filter_extra_fields = True
517 518 overwrite_default_branch = v.StringBoolean(if_missing=False)
518 519 default_branch_perm = v.OneOf(branch_perms_choices)
519 520
520 521 return _BranchPermissionsForm
521 522
522 523
523 524 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
524 525 repo_group_create_choices, user_group_create_choices,
525 526 fork_choices, inherit_default_permissions_choices):
526 527 _ = localizer
527 528
528 529 class _DefaultPermissionsForm(formencode.Schema):
529 530 allow_extra_fields = True
530 531 filter_extra_fields = True
531 532
532 533 anonymous = v.StringBoolean(if_missing=False)
533 534
534 535 default_repo_create = v.OneOf(create_choices)
535 536 default_repo_create_on_write = v.OneOf(create_on_write_choices)
536 537 default_user_group_create = v.OneOf(user_group_create_choices)
537 538 default_repo_group_create = v.OneOf(repo_group_create_choices)
538 539 default_fork_create = v.OneOf(fork_choices)
539 540 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
540 541 return _DefaultPermissionsForm
541 542
542 543
543 544 def UserIndividualPermissionsForm(localizer):
544 545 _ = localizer
545 546
546 547 class _DefaultPermissionsForm(formencode.Schema):
547 548 allow_extra_fields = True
548 549 filter_extra_fields = True
549 550
550 551 inherit_default_permissions = v.StringBoolean(if_missing=False)
551 552 return _DefaultPermissionsForm
552 553
553 554
554 555 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
555 556 _ = localizer
556 557 old_data = old_data or {}
557 558
558 559 class _DefaultsForm(formencode.Schema):
559 560 allow_extra_fields = True
560 561 filter_extra_fields = True
561 562 default_repo_type = v.OneOf(supported_backends)
562 563 default_repo_private = v.StringBoolean(if_missing=False)
563 564 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
564 565 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
565 566 default_repo_enable_locking = v.StringBoolean(if_missing=False)
566 567 return _DefaultsForm
567 568
568 569
569 570 def AuthSettingsForm(localizer):
570 571 _ = localizer
571 572
572 573 class _AuthSettingsForm(formencode.Schema):
573 574 allow_extra_fields = True
574 575 filter_extra_fields = True
575 576 auth_plugins = All(v.ValidAuthPlugins(localizer),
576 577 v.UniqueListFromString(localizer)(not_empty=True))
577 578 return _AuthSettingsForm
578 579
579 580
580 581 def UserExtraEmailForm(localizer):
581 582 _ = localizer
582 583
583 584 class _UserExtraEmailForm(formencode.Schema):
584 585 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
585 586 return _UserExtraEmailForm
586 587
587 588
588 589 def UserExtraIpForm(localizer):
589 590 _ = localizer
590 591
591 592 class _UserExtraIpForm(formencode.Schema):
592 593 ip = v.ValidIp(localizer)(not_empty=True)
593 594 return _UserExtraIpForm
594 595
595 596
596 597 def PullRequestForm(localizer, repo_id):
597 598 _ = localizer
598 599
599 600 class ReviewerForm(formencode.Schema):
600 601 user_id = v.Int(not_empty=True)
601 602 reasons = All()
602 603 rules = All(v.UniqueList(localizer, convert=int)())
603 604 mandatory = v.StringBoolean()
604 605 role = v.String(if_missing='reviewer')
605 606
606 607 class ObserverForm(formencode.Schema):
607 608 user_id = v.Int(not_empty=True)
608 609 reasons = All()
609 610 rules = All(v.UniqueList(localizer, convert=int)())
610 611 mandatory = v.StringBoolean()
611 612 role = v.String(if_missing='observer')
612 613
613 614 class _PullRequestForm(formencode.Schema):
614 615 allow_extra_fields = True
615 616 filter_extra_fields = True
616 617
617 618 common_ancestor = v.UnicodeString(strip=True, required=True)
618 619 source_repo = v.UnicodeString(strip=True, required=True)
619 620 source_ref = v.UnicodeString(strip=True, required=True)
620 621 target_repo = v.UnicodeString(strip=True, required=True)
621 622 target_ref = v.UnicodeString(strip=True, required=True)
622 623 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
623 624 v.UniqueList(localizer)(not_empty=True))
624 625 review_members = formencode.ForEach(ReviewerForm())
625 626 observer_members = formencode.ForEach(ObserverForm())
626 627 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
627 628 pullrequest_desc = v.UnicodeString(strip=True, required=False)
628 629 description_renderer = v.UnicodeString(strip=True, required=False)
629 630
630 631 return _PullRequestForm
631 632
632 633
633 634 def IssueTrackerPatternsForm(localizer):
634 635 _ = localizer
635 636
636 637 class _IssueTrackerPatternsForm(formencode.Schema):
637 638 allow_extra_fields = True
638 639 filter_extra_fields = False
639 640 chained_validators = [v.ValidPattern(localizer)]
640 641 return _IssueTrackerPatternsForm
@@ -1,450 +1,453 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Model for notifications
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import premailer
30 30 from pyramid.threadlocal import get_current_request
31 31 from sqlalchemy.sql.expression import false, true
32 32
33 33 import rhodecode
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import Notification, User, UserNotification
37 37 from rhodecode.model.meta import Session
38 38 from rhodecode.translation import TranslationString
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42
43 43 class NotificationModel(BaseModel):
44 44
45 45 cls = Notification
46 46
47 47 def __get_notification(self, notification):
48 48 if isinstance(notification, Notification):
49 49 return notification
50 50 elif isinstance(notification, (int, long)):
51 51 return Notification.get(notification)
52 52 else:
53 53 if notification:
54 54 raise Exception('notification must be int, long or Instance'
55 55 ' of Notification got %s' % type(notification))
56 56
57 57 def create(
58 58 self, created_by, notification_subject='', notification_body='',
59 59 notification_type=Notification.TYPE_MESSAGE, recipients=None,
60 60 mention_recipients=None, with_email=True, email_kwargs=None):
61 61 """
62 62
63 63 Creates notification of given type
64 64
65 65 :param created_by: int, str or User instance. User who created this
66 66 notification
67 67 :param notification_subject: subject of notification itself,
68 68 it will be generated automatically from notification_type if not specified
69 69 :param notification_body: body of notification text
70 70 it will be generated automatically from notification_type if not specified
71 71 :param notification_type: type of notification, based on that we
72 72 pick templates
73 73 :param recipients: list of int, str or User objects, when None
74 74 is given send to all admins
75 75 :param mention_recipients: list of int, str or User objects,
76 76 that were mentioned
77 77 :param with_email: send email with this notification
78 78 :param email_kwargs: dict with arguments to generate email
79 79 """
80 80
81 81 from rhodecode.lib.celerylib import tasks, run_task
82 82
83 83 if recipients and not getattr(recipients, '__iter__', False):
84 84 raise Exception('recipients must be an iterable object')
85 85
86 86 if not (notification_subject and notification_body) and not notification_type:
87 87 raise ValueError('notification_subject, and notification_body '
88 88 'cannot be empty when notification_type is not specified')
89 89
90 90 created_by_obj = self._get_user(created_by)
91 91
92 92 if not created_by_obj:
93 93 raise Exception('unknown user %s' % created_by)
94 94
95 95 # default MAIN body if not given
96 96 email_kwargs = email_kwargs or {'body': notification_body}
97 97 mention_recipients = mention_recipients or set()
98 98
99 99 if recipients is None:
100 100 # recipients is None means to all admins
101 101 recipients_objs = User.query().filter(User.admin == true()).all()
102 102 log.debug('sending notifications %s to admins: %s',
103 103 notification_type, recipients_objs)
104 104 else:
105 105 recipients_objs = set()
106 106 for u in recipients:
107 107 obj = self._get_user(u)
108 108 if obj:
109 109 recipients_objs.add(obj)
110 110 else: # we didn't find this user, log the error and carry on
111 111 log.error('cannot notify unknown user %r', u)
112 112
113 113 if not recipients_objs:
114 114 raise Exception('no valid recipients specified')
115 115
116 116 log.debug('sending notifications %s to %s',
117 117 notification_type, recipients_objs)
118 118
119 119 # add mentioned users into recipients
120 120 final_recipients = set(recipients_objs).union(mention_recipients)
121 121
122 122 (subject, email_body, email_body_plaintext) = \
123 123 EmailNotificationModel().render_email(notification_type, **email_kwargs)
124 124
125 125 if not notification_subject:
126 126 notification_subject = subject
127 127
128 128 if not notification_body:
129 129 notification_body = email_body_plaintext
130 130
131 131 notification = Notification.create(
132 132 created_by=created_by_obj, subject=notification_subject,
133 133 body=notification_body, recipients=final_recipients,
134 134 type_=notification_type
135 135 )
136 136
137 137 if not with_email: # skip sending email, and just create notification
138 138 return notification
139 139
140 140 # don't send email to person who created this comment
141 141 rec_objs = set(recipients_objs).difference({created_by_obj})
142 142
143 143 # now notify all recipients in question
144 144
145 145 for recipient in rec_objs.union(mention_recipients):
146 146 # inject current recipient
147 147 email_kwargs['recipient'] = recipient
148 148 email_kwargs['mention'] = recipient in mention_recipients
149 149 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
150 150 notification_type, **email_kwargs)
151 151
152 152 extra_headers = None
153 153 if 'thread_ids' in email_kwargs:
154 154 extra_headers = {'thread_ids': email_kwargs.pop('thread_ids')}
155 155
156 156 log.debug('Creating notification email task for user:`%s`', recipient)
157 157 task = run_task(
158 158 tasks.send_email, recipient.email, subject,
159 159 email_body_plaintext, email_body, extra_headers=extra_headers)
160 160 log.debug('Created email task: %s', task)
161 161
162 162 return notification
163 163
164 164 def delete(self, user, notification):
165 165 # we don't want to remove actual notification just the assignment
166 166 try:
167 167 notification = self.__get_notification(notification)
168 168 user = self._get_user(user)
169 169 if notification and user:
170 170 obj = UserNotification.query()\
171 171 .filter(UserNotification.user == user)\
172 172 .filter(UserNotification.notification == notification)\
173 173 .one()
174 174 Session().delete(obj)
175 175 return True
176 176 except Exception:
177 177 log.error(traceback.format_exc())
178 178 raise
179 179
180 180 def get_for_user(self, user, filter_=None):
181 181 """
182 182 Get mentions for given user, filter them if filter dict is given
183 183 """
184 184 user = self._get_user(user)
185 185
186 186 q = UserNotification.query()\
187 187 .filter(UserNotification.user == user)\
188 188 .join((
189 189 Notification, UserNotification.notification_id ==
190 190 Notification.notification_id))
191 191 if filter_ == ['all']:
192 192 q = q # no filter
193 193 elif filter_ == ['unread']:
194 194 q = q.filter(UserNotification.read == false())
195 195 elif filter_:
196 196 q = q.filter(Notification.type_.in_(filter_))
197 197
198 198 return q
199 199
200 200 def mark_read(self, user, notification):
201 201 try:
202 202 notification = self.__get_notification(notification)
203 203 user = self._get_user(user)
204 204 if notification and user:
205 205 obj = UserNotification.query()\
206 206 .filter(UserNotification.user == user)\
207 207 .filter(UserNotification.notification == notification)\
208 208 .one()
209 209 obj.read = True
210 210 Session().add(obj)
211 211 return True
212 212 except Exception:
213 213 log.error(traceback.format_exc())
214 214 raise
215 215
216 216 def mark_all_read_for_user(self, user, filter_=None):
217 217 user = self._get_user(user)
218 218 q = UserNotification.query()\
219 219 .filter(UserNotification.user == user)\
220 220 .filter(UserNotification.read == false())\
221 221 .join((
222 222 Notification, UserNotification.notification_id ==
223 223 Notification.notification_id))
224 224 if filter_ == ['unread']:
225 225 q = q.filter(UserNotification.read == false())
226 226 elif filter_:
227 227 q = q.filter(Notification.type_.in_(filter_))
228 228
229 229 # this is a little inefficient but sqlalchemy doesn't support
230 230 # update on joined tables :(
231 231 for obj in q.all():
232 232 obj.read = True
233 233 Session().add(obj)
234 234
235 235 def get_unread_cnt_for_user(self, user):
236 236 user = self._get_user(user)
237 237 return UserNotification.query()\
238 238 .filter(UserNotification.read == false())\
239 239 .filter(UserNotification.user == user).count()
240 240
241 241 def get_unread_for_user(self, user):
242 242 user = self._get_user(user)
243 243 return [x.notification for x in UserNotification.query()
244 244 .filter(UserNotification.read == false())
245 245 .filter(UserNotification.user == user).all()]
246 246
247 247 def get_user_notification(self, user, notification):
248 248 user = self._get_user(user)
249 249 notification = self.__get_notification(notification)
250 250
251 251 return UserNotification.query()\
252 252 .filter(UserNotification.notification == notification)\
253 253 .filter(UserNotification.user == user).scalar()
254 254
255 255 def make_description(self, notification, translate, show_age=True):
256 256 """
257 257 Creates a human readable description based on properties
258 258 of notification object
259 259 """
260 260 _ = translate
261 261 _map = {
262 262 notification.TYPE_CHANGESET_COMMENT: [
263 263 _('%(user)s commented on commit %(date_or_age)s'),
264 264 _('%(user)s commented on commit at %(date_or_age)s'),
265 265 ],
266 266 notification.TYPE_MESSAGE: [
267 267 _('%(user)s sent message %(date_or_age)s'),
268 268 _('%(user)s sent message at %(date_or_age)s'),
269 269 ],
270 270 notification.TYPE_MENTION: [
271 271 _('%(user)s mentioned you %(date_or_age)s'),
272 272 _('%(user)s mentioned you at %(date_or_age)s'),
273 273 ],
274 274 notification.TYPE_REGISTRATION: [
275 275 _('%(user)s registered in RhodeCode %(date_or_age)s'),
276 276 _('%(user)s registered in RhodeCode at %(date_or_age)s'),
277 277 ],
278 278 notification.TYPE_PULL_REQUEST: [
279 279 _('%(user)s opened new pull request %(date_or_age)s'),
280 280 _('%(user)s opened new pull request at %(date_or_age)s'),
281 281 ],
282 282 notification.TYPE_PULL_REQUEST_UPDATE: [
283 283 _('%(user)s updated pull request %(date_or_age)s'),
284 284 _('%(user)s updated pull request at %(date_or_age)s'),
285 285 ],
286 286 notification.TYPE_PULL_REQUEST_COMMENT: [
287 287 _('%(user)s commented on pull request %(date_or_age)s'),
288 288 _('%(user)s commented on pull request at %(date_or_age)s'),
289 289 ],
290 290 }
291 291
292 292 templates = _map[notification.type_]
293 293
294 294 if show_age:
295 295 template = templates[0]
296 296 date_or_age = h.age(notification.created_on)
297 297 if translate:
298 298 date_or_age = translate(date_or_age)
299 299
300 300 if isinstance(date_or_age, TranslationString):
301 301 date_or_age = date_or_age.interpolate()
302 302
303 303 else:
304 304 template = templates[1]
305 305 date_or_age = h.format_date(notification.created_on)
306 306
307 307 return template % {
308 308 'user': notification.created_by_user.username,
309 309 'date_or_age': date_or_age,
310 310 }
311 311
312 312
313 313 # Templates for Titles, that could be overwritten by rcextensions
314 314 # Title of email for pull-request update
315 315 EMAIL_PR_UPDATE_SUBJECT_TEMPLATE = ''
316 316 # Title of email for request for pull request review
317 317 EMAIL_PR_REVIEW_SUBJECT_TEMPLATE = ''
318 318
319 319 # Title of email for general comment on pull request
320 320 EMAIL_PR_COMMENT_SUBJECT_TEMPLATE = ''
321 321 # Title of email for general comment which includes status change on pull request
322 322 EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
323 323 # Title of email for inline comment on a file in pull request
324 324 EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE = ''
325 325
326 326 # Title of email for general comment on commit
327 327 EMAIL_COMMENT_SUBJECT_TEMPLATE = ''
328 328 # Title of email for general comment which includes status change on commit
329 329 EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE = ''
330 330 # Title of email for inline comment on a file in commit
331 331 EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE = ''
332 332
333 333
334 334 class EmailNotificationModel(BaseModel):
335 335 TYPE_COMMIT_COMMENT = Notification.TYPE_CHANGESET_COMMENT
336 336 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
337 337 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
338 338 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
339 339 TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE
340 340 TYPE_MAIN = Notification.TYPE_MESSAGE
341 341
342 342 TYPE_PASSWORD_RESET = 'password_reset'
343 343 TYPE_PASSWORD_RESET_CONFIRMATION = 'password_reset_confirmation'
344 344 TYPE_EMAIL_TEST = 'email_test'
345 345 TYPE_EMAIL_EXCEPTION = 'exception'
346 TYPE_UPDATE_AVAILABLE = 'update_available'
346 347 TYPE_TEST = 'test'
347 348
348 349 email_types = {
349 350 TYPE_MAIN:
350 351 'rhodecode:templates/email_templates/main.mako',
351 352 TYPE_TEST:
352 353 'rhodecode:templates/email_templates/test.mako',
353 354 TYPE_EMAIL_EXCEPTION:
354 355 'rhodecode:templates/email_templates/exception_tracker.mako',
356 TYPE_UPDATE_AVAILABLE:
357 'rhodecode:templates/email_templates/update_available.mako',
355 358 TYPE_EMAIL_TEST:
356 359 'rhodecode:templates/email_templates/email_test.mako',
357 360 TYPE_REGISTRATION:
358 361 'rhodecode:templates/email_templates/user_registration.mako',
359 362 TYPE_PASSWORD_RESET:
360 363 'rhodecode:templates/email_templates/password_reset.mako',
361 364 TYPE_PASSWORD_RESET_CONFIRMATION:
362 365 'rhodecode:templates/email_templates/password_reset_confirmation.mako',
363 366 TYPE_COMMIT_COMMENT:
364 367 'rhodecode:templates/email_templates/commit_comment.mako',
365 368 TYPE_PULL_REQUEST:
366 369 'rhodecode:templates/email_templates/pull_request_review.mako',
367 370 TYPE_PULL_REQUEST_COMMENT:
368 371 'rhodecode:templates/email_templates/pull_request_comment.mako',
369 372 TYPE_PULL_REQUEST_UPDATE:
370 373 'rhodecode:templates/email_templates/pull_request_update.mako',
371 374 }
372 375
373 376 premailer_instance = premailer.Premailer(
374 377 cssutils_logging_level=logging.ERROR,
375 378 cssutils_logging_handler=logging.getLogger().handlers[0]
376 379 if logging.getLogger().handlers else None,
377 380 )
378 381
379 382 def __init__(self):
380 383 """
381 384 Example usage::
382 385
383 386 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
384 387 EmailNotificationModel.TYPE_TEST, **email_kwargs)
385 388
386 389 """
387 390 super(EmailNotificationModel, self).__init__()
388 391 self.rhodecode_instance_name = rhodecode.CONFIG.get('rhodecode_title')
389 392
390 393 def _update_kwargs_for_render(self, kwargs):
391 394 """
392 395 Inject params required for Mako rendering
393 396
394 397 :param kwargs:
395 398 """
396 399
397 400 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
398 401 kwargs['rhodecode_version'] = rhodecode.__version__
399 402 instance_url = h.route_url('home')
400 403 _kwargs = {
401 404 'instance_url': instance_url,
402 405 'whitespace_filter': self.whitespace_filter,
403 406 'email_pr_update_subject_template': EMAIL_PR_UPDATE_SUBJECT_TEMPLATE,
404 407 'email_pr_review_subject_template': EMAIL_PR_REVIEW_SUBJECT_TEMPLATE,
405 408 'email_pr_comment_subject_template': EMAIL_PR_COMMENT_SUBJECT_TEMPLATE,
406 409 'email_pr_comment_status_change_subject_template': EMAIL_PR_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE,
407 410 'email_pr_comment_file_subject_template': EMAIL_PR_COMMENT_FILE_SUBJECT_TEMPLATE,
408 411 'email_comment_subject_template': EMAIL_COMMENT_SUBJECT_TEMPLATE,
409 412 'email_comment_status_change_subject_template': EMAIL_COMMENT_STATUS_CHANGE_SUBJECT_TEMPLATE,
410 413 'email_comment_file_subject_template': EMAIL_COMMENT_FILE_SUBJECT_TEMPLATE,
411 414 }
412 415 _kwargs.update(kwargs)
413 416 return _kwargs
414 417
415 418 def whitespace_filter(self, text):
416 419 return text.replace('\n', '').replace('\t', '')
417 420
418 421 def get_renderer(self, type_, request):
419 422 template_name = self.email_types[type_]
420 423 return request.get_partial_renderer(template_name)
421 424
422 425 def render_email(self, type_, **kwargs):
423 426 """
424 427 renders template for email, and returns a tuple of
425 428 (subject, email_headers, email_html_body, email_plaintext_body)
426 429 """
427 430 # translator and helpers inject
428 431 _kwargs = self._update_kwargs_for_render(kwargs)
429 432 request = get_current_request()
430 433 email_template = self.get_renderer(type_, request=request)
431 434
432 435 subject = email_template.render('subject', **_kwargs)
433 436
434 437 try:
435 438 body_plaintext = email_template.render('body_plaintext', **_kwargs)
436 439 except AttributeError:
437 440 # it's not defined in template, ok we can skip it
438 441 body_plaintext = ''
439 442
440 443 # render WHOLE template
441 444 body = email_template.render(None, **_kwargs)
442 445
443 446 try:
444 447 # Inline CSS styles and conversion
445 448 body = self.premailer_instance.transform(body)
446 449 except Exception:
447 450 log.exception('Failed to parse body with premailer')
448 451 pass
449 452
450 453 return subject, body, body_plaintext
@@ -1,83 +1,83 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import urllib2
23 23 from packaging.version import Version
24 24
25 25 import rhodecode
26 26 from rhodecode.lib.ext_json import json
27 27 from rhodecode.model import BaseModel
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.settings import SettingsModel
30 30
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class UpdateModel(BaseModel):
36 36 UPDATE_SETTINGS_KEY = 'update_version'
37 37 UPDATE_URL_SETTINGS_KEY = 'rhodecode_update_url'
38 38
39 39 @staticmethod
40 40 def get_update_data(update_url):
41 41 """Return the JSON update data."""
42 42 ver = rhodecode.__version__
43 43 log.debug('Checking for upgrade on `%s` server', update_url)
44 44 opener = urllib2.build_opener()
45 45 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
46 46 response = opener.open(update_url)
47 47 response_data = response.read()
48 48 data = json.loads(response_data)
49 49 log.debug('update server returned data')
50 50 return data
51 51
52 52 def get_update_url(self):
53 53 settings = SettingsModel().get_all_settings()
54 54 return settings.get(self.UPDATE_URL_SETTINGS_KEY)
55 55
56 56 def store_version(self, version):
57 57 log.debug('Storing version %s into settings', version)
58 58 setting = SettingsModel().create_or_update_setting(
59 59 self.UPDATE_SETTINGS_KEY, version)
60 60 Session().add(setting)
61 61 Session().commit()
62 62
63 def get_stored_version(self):
63 def get_stored_version(self, fallback=None):
64 64 obj = SettingsModel().get_setting_by_name(self.UPDATE_SETTINGS_KEY)
65 65 if obj:
66 66 return obj.app_settings_value
67 return '0.0.0'
67 return fallback or '0.0.0'
68 68
69 69 def _sanitize_version(self, version):
70 70 """
71 71 Cleanup our custom ver.
72 72 e.g 4.11.0_20171204_204825_CE_default_EE_default to 4.11.0
73 73 """
74 74 return version.split('_')[0]
75 75
76 76 def is_outdated(self, cur_version, latest_version=None):
77 77 latest_version = latest_version or self.get_stored_version()
78 78 try:
79 79 cur_version = self._sanitize_version(cur_version)
80 80 return Version(latest_version) > Version(cur_version)
81 81 except Exception:
82 82 # could be invalid version, etc
83 83 return False
@@ -1,1502 +1,1639 b''
1 1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 var firefoxAnchorFix = function() {
20 20 // hack to make anchor links behave properly on firefox, in our inline
21 21 // comments generation when comments are injected firefox is misbehaving
22 22 // when jumping to anchor links
23 23 if (location.href.indexOf('#') > -1) {
24 24 location.href += '';
25 25 }
26 26 };
27 27
28
28 29 var linkifyComments = function(comments) {
29 30 var firstCommentId = null;
30 31 if (comments) {
31 32 firstCommentId = $(comments[0]).data('comment-id');
32 33 }
33 34
34 35 if (firstCommentId){
35 36 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
36 37 }
37 38 };
38 39
40
39 41 var bindToggleButtons = function() {
40 42 $('.comment-toggle').on('click', function() {
41 43 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
42 44 });
43 45 };
44 46
45 47
46
47 48 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
48 49 failHandler = failHandler || function() {};
49 50 postData = toQueryString(postData);
50 51 var request = $.ajax({
51 52 url: url,
52 53 type: 'POST',
53 54 data: postData,
54 55 headers: {'X-PARTIAL-XHR': true}
55 56 })
56 57 .done(function (data) {
57 58 successHandler(data);
58 59 })
59 60 .fail(function (data, textStatus, errorThrown) {
60 61 failHandler(data, textStatus, errorThrown)
61 62 });
62 63 return request;
63 64 };
64 65
65 66
66
67
68 67 /* Comment form for main and inline comments */
69 68 (function(mod) {
70 69
71 70 if (typeof exports == "object" && typeof module == "object") {
72 71 // CommonJS
73 72 module.exports = mod();
74 73 }
75 74 else {
76 75 // Plain browser env
77 76 (this || window).CommentForm = mod();
78 77 }
79 78
80 79 })(function() {
81 80 "use strict";
82 81
83 82 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
84 83
85 84 if (!(this instanceof CommentForm)) {
86 85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
87 86 }
88 87
89 88 // bind the element instance to our Form
90 89 $(formElement).get(0).CommentForm = this;
91 90
92 91 this.withLineNo = function(selector) {
93 92 var lineNo = this.lineNo;
94 93 if (lineNo === undefined) {
95 94 return selector
96 95 } else {
97 96 return selector + '_' + lineNo;
98 97 }
99 98 };
100 99
101 100 this.commitId = commitId;
102 101 this.pullRequestId = pullRequestId;
103 102 this.lineNo = lineNo;
104 103 this.initAutocompleteActions = initAutocompleteActions;
105 104
106 105 this.previewButton = this.withLineNo('#preview-btn');
107 106 this.previewContainer = this.withLineNo('#preview-container');
108 107
109 108 this.previewBoxSelector = this.withLineNo('#preview-box');
110 109
111 110 this.editButton = this.withLineNo('#edit-btn');
112 111 this.editContainer = this.withLineNo('#edit-container');
113 112 this.cancelButton = this.withLineNo('#cancel-btn');
114 113 this.commentType = this.withLineNo('#comment_type');
115 114
116 115 this.resolvesId = null;
117 116 this.resolvesActionId = null;
118 117
119 118 this.closesPr = '#close_pull_request';
120 119
121 120 this.cmBox = this.withLineNo('#text');
122 121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
123 122
124 123 this.statusChange = this.withLineNo('#change_status');
125 124
126 125 this.submitForm = formElement;
127 126
128 127 this.submitButton = $(this.submitForm).find('.submit-comment-action');
129 128 this.submitButtonText = this.submitButton.val();
130 129
131 130 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
132 131 this.submitDraftButtonText = this.submitDraftButton.val();
133 132
134 133 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
135 134 {'repo_name': templateContext.repo_name,
136 135 'commit_id': templateContext.commit_data.commit_id});
137 136
138 137 if (edit){
139 138 this.submitDraftButton.hide();
140 139 this.submitButtonText = _gettext('Update Comment');
141 140 $(this.commentType).prop('disabled', true);
142 141 $(this.commentType).addClass('disabled');
143 142 var editInfo =
144 143 '';
145 144 $(editInfo).insertBefore($(this.editButton).parent());
146 145 }
147 146
148 147 if (resolvesCommentId){
149 148 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
150 149 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
151 150 $(this.commentType).prop('disabled', true);
152 151 $(this.commentType).addClass('disabled');
153 152
154 153 // disable select
155 154 setTimeout(function() {
156 155 $(self.statusChange).select2('readonly', true);
157 156 }, 10);
158 157
159 158 var resolvedInfo = (
160 159 '<li class="resolve-action">' +
161 160 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
162 161 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
163 162 '</li>'
164 163 ).format(resolvesCommentId, _gettext('resolve comment'));
165 164 $(resolvedInfo).insertAfter($(this.commentType).parent());
166 165 }
167 166
168 167 // based on commitId, or pullRequestId decide where do we submit
169 168 // out data
170 169 if (this.commitId){
171 170 var pyurl = 'repo_commit_comment_create';
172 171 if(edit){
173 172 pyurl = 'repo_commit_comment_edit';
174 173 }
175 174 this.submitUrl = pyroutes.url(pyurl,
176 175 {'repo_name': templateContext.repo_name,
177 176 'commit_id': this.commitId,
178 177 'comment_id': comment_id});
179 178 this.selfUrl = pyroutes.url('repo_commit',
180 179 {'repo_name': templateContext.repo_name,
181 180 'commit_id': this.commitId});
182 181
183 182 } else if (this.pullRequestId) {
184 183 var pyurl = 'pullrequest_comment_create';
185 184 if(edit){
186 185 pyurl = 'pullrequest_comment_edit';
187 186 }
188 187 this.submitUrl = pyroutes.url(pyurl,
189 188 {'repo_name': templateContext.repo_name,
190 189 'pull_request_id': this.pullRequestId,
191 190 'comment_id': comment_id});
192 191 this.selfUrl = pyroutes.url('pullrequest_show',
193 192 {'repo_name': templateContext.repo_name,
194 193 'pull_request_id': this.pullRequestId});
195 194
196 195 } else {
197 196 throw new Error(
198 197 'CommentForm requires pullRequestId, or commitId to be specified.')
199 198 }
200 199
201 200 // FUNCTIONS and helpers
202 201 var self = this;
203 202
204 203 this.isInline = function(){
205 204 return this.lineNo && this.lineNo != 'general';
206 205 };
207 206
208 207 this.getCmInstance = function(){
209 208 return this.cm
210 209 };
211 210
212 211 this.setPlaceholder = function(placeholder) {
213 212 var cm = this.getCmInstance();
214 213 if (cm){
215 214 cm.setOption('placeholder', placeholder);
216 215 }
217 216 };
218 217
219 218 this.getCommentStatus = function() {
220 219 return $(this.submitForm).find(this.statusChange).val();
221 220 };
222 221
223 222 this.getCommentType = function() {
224 223 return $(this.submitForm).find(this.commentType).val();
225 224 };
226 225
227 226 this.getDraftState = function () {
228 227 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
229 228 var data = $(submitterElem).data('isDraft');
230 229 return data
231 230 }
232 231
233 232 this.getResolvesId = function() {
234 233 return $(this.submitForm).find(this.resolvesId).val() || null;
235 234 };
236 235
237 236 this.getClosePr = function() {
238 237 return $(this.submitForm).find(this.closesPr).val() || null;
239 238 };
240 239
241 240 this.markCommentResolved = function(resolvedCommentId){
242 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
243 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
241 Rhodecode.comments.markCommentResolved(resolvedCommentId)
244 242 };
245 243
246 244 this.isAllowedToSubmit = function() {
247 245 var commentDisabled = $(this.submitButton).prop('disabled');
248 246 var draftDisabled = $(this.submitDraftButton).prop('disabled');
249 247 return !commentDisabled && !draftDisabled;
250 248 };
251 249
252 250 this.initStatusChangeSelector = function(){
253 251 var formatChangeStatus = function(state, escapeMarkup) {
254 252 var originalOption = state.element;
255 253 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
256 254 return tmpl
257 255 };
258 256 var formatResult = function(result, container, query, escapeMarkup) {
259 257 return formatChangeStatus(result, escapeMarkup);
260 258 };
261 259
262 260 var formatSelection = function(data, container, escapeMarkup) {
263 261 return formatChangeStatus(data, escapeMarkup);
264 262 };
265 263
266 264 $(this.submitForm).find(this.statusChange).select2({
267 265 placeholder: _gettext('Status Review'),
268 266 formatResult: formatResult,
269 267 formatSelection: formatSelection,
270 268 containerCssClass: "drop-menu status_box_menu",
271 269 dropdownCssClass: "drop-menu-dropdown",
272 270 dropdownAutoWidth: true,
273 271 minimumResultsForSearch: -1
274 272 });
275 273
276 274 $(this.submitForm).find(this.statusChange).on('change', function() {
277 275 var status = self.getCommentStatus();
278 276
279 277 if (status && !self.isInline()) {
280 278 $(self.submitButton).prop('disabled', false);
281 279 $(self.submitDraftButton).prop('disabled', false);
282 280 }
283 281
284 282 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
285 283 self.setPlaceholder(placeholderText)
286 284 })
287 285 };
288 286
289 287 // reset the comment form into it's original state
290 288 this.resetCommentFormState = function(content) {
291 289 content = content || '';
292 290
293 291 $(this.editContainer).show();
294 292 $(this.editButton).parent().addClass('active');
295 293
296 294 $(this.previewContainer).hide();
297 295 $(this.previewButton).parent().removeClass('active');
298 296
299 297 this.setActionButtonsDisabled(true);
300 298 self.cm.setValue(content);
301 299 self.cm.setOption("readOnly", false);
302 300
303 301 if (this.resolvesId) {
304 302 // destroy the resolve action
305 303 $(this.resolvesId).parent().remove();
306 304 }
307 305 // reset closingPR flag
308 306 $('.close-pr-input').remove();
309 307
310 308 $(this.statusChange).select2('readonly', false);
311 309 };
312 310
313 311 this.globalSubmitSuccessCallback = function(comment){
314 312 // default behaviour is to call GLOBAL hook, if it's registered.
315 313 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
316 314 commentFormGlobalSubmitSuccessCallback(comment);
317 315 }
318 316 };
319 317
320 318 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
321 319 return _submitAjaxPOST(url, postData, successHandler, failHandler);
322 320 };
323 321
324 322 // overwrite a submitHandler, we need to do it for inline comments
325 323 this.setHandleFormSubmit = function(callback) {
326 324 this.handleFormSubmit = callback;
327 325 };
328 326
329 327 // overwrite a submitSuccessHandler
330 328 this.setGlobalSubmitSuccessCallback = function(callback) {
331 329 this.globalSubmitSuccessCallback = callback;
332 330 };
333 331
334 332 // default handler for for submit for main comments
335 333 this.handleFormSubmit = function() {
336 334 var text = self.cm.getValue();
337 335 var status = self.getCommentStatus();
338 336 var commentType = self.getCommentType();
339 337 var isDraft = self.getDraftState();
340 338 var resolvesCommentId = self.getResolvesId();
341 339 var closePullRequest = self.getClosePr();
342 340
343 341 if (text === "" && !status) {
344 342 return;
345 343 }
346 344
347 345 var excludeCancelBtn = false;
348 346 var submitEvent = true;
349 347 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
350 348 self.cm.setOption("readOnly", true);
351 349
352 350 var postData = {
353 351 'text': text,
354 352 'changeset_status': status,
355 353 'comment_type': commentType,
356 354 'csrf_token': CSRF_TOKEN
357 355 };
358 356
359 357 if (resolvesCommentId) {
360 358 postData['resolves_comment_id'] = resolvesCommentId;
361 359 }
362 360
363 361 if (closePullRequest) {
364 362 postData['close_pull_request'] = true;
365 363 }
366 364
367 365 // submitSuccess for general comments
368 366 var submitSuccessCallback = function(json_data) {
369 367 // reload page if we change status for single commit.
370 368 if (status && self.commitId) {
371 369 location.reload(true);
372 370 } else {
373 371 // inject newly created comments, json_data is {<comment_id>: {}}
374 372 Rhodecode.comments.attachGeneralComment(json_data)
375 373
376 374 self.resetCommentFormState();
377 375 timeagoActivate();
378 376 tooltipActivate();
379 377
380 378 // mark visually which comment was resolved
381 379 if (resolvesCommentId) {
382 380 self.markCommentResolved(resolvesCommentId);
383 381 }
384 382 }
385 383
386 384 // run global callback on submit
387 385 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
388 386
389 387 };
390 388 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
391 389 var prefix = "Error while submitting comment.\n"
392 390 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
393 391 ajaxErrorSwal(message);
394 392 self.resetCommentFormState(text);
395 393 };
396 394 self.submitAjaxPOST(
397 395 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
398 396 };
399 397
400 398 this.previewSuccessCallback = function(o) {
401 399 $(self.previewBoxSelector).html(o);
402 400 $(self.previewBoxSelector).removeClass('unloaded');
403 401
404 402 // swap buttons, making preview active
405 403 $(self.previewButton).parent().addClass('active');
406 404 $(self.editButton).parent().removeClass('active');
407 405
408 406 // unlock buttons
409 407 self.setActionButtonsDisabled(false);
410 408 };
411 409
412 410 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
413 411 excludeCancelBtn = excludeCancelBtn || false;
414 412 submitEvent = submitEvent || false;
415 413
416 414 $(this.editButton).prop('disabled', state);
417 415 $(this.previewButton).prop('disabled', state);
418 416
419 417 if (!excludeCancelBtn) {
420 418 $(this.cancelButton).prop('disabled', state);
421 419 }
422 420
423 421 var submitState = state;
424 422 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
425 423 // if the value of commit review status is set, we allow
426 424 // submit button, but only on Main form, isInline means inline
427 425 submitState = false
428 426 }
429 427
430 428 $(this.submitButton).prop('disabled', submitState);
431 429 $(this.submitDraftButton).prop('disabled', submitState);
432 430
433 431 if (submitEvent) {
434 432 var isDraft = self.getDraftState();
435 433
436 434 if (isDraft) {
437 435 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
438 436 } else {
439 437 $(this.submitButton).val(_gettext('Submitting...'));
440 438 }
441 439
442 440 } else {
443 441 $(this.submitButton).val(this.submitButtonText);
444 442 $(this.submitDraftButton).val(this.submitDraftButtonText);
445 443 }
446 444
447 445 };
448 446
449 447 // lock preview/edit/submit buttons on load, but exclude cancel button
450 448 var excludeCancelBtn = true;
451 449 this.setActionButtonsDisabled(true, excludeCancelBtn);
452 450
453 451 // anonymous users don't have access to initialized CM instance
454 452 if (this.cm !== undefined){
455 453 this.cm.on('change', function(cMirror) {
456 454 if (cMirror.getValue() === "") {
457 455 self.setActionButtonsDisabled(true, excludeCancelBtn)
458 456 } else {
459 457 self.setActionButtonsDisabled(false, excludeCancelBtn)
460 458 }
461 459 });
462 460 }
463 461
464 462 $(this.editButton).on('click', function(e) {
465 463 e.preventDefault();
466 464
467 465 $(self.previewButton).parent().removeClass('active');
468 466 $(self.previewContainer).hide();
469 467
470 468 $(self.editButton).parent().addClass('active');
471 469 $(self.editContainer).show();
472 470
473 471 });
474 472
475 473 $(this.previewButton).on('click', function(e) {
476 474 e.preventDefault();
477 475 var text = self.cm.getValue();
478 476
479 477 if (text === "") {
480 478 return;
481 479 }
482 480
483 481 var postData = {
484 482 'text': text,
485 483 'renderer': templateContext.visual.default_renderer,
486 484 'csrf_token': CSRF_TOKEN
487 485 };
488 486
489 487 // lock ALL buttons on preview
490 488 self.setActionButtonsDisabled(true);
491 489
492 490 $(self.previewBoxSelector).addClass('unloaded');
493 491 $(self.previewBoxSelector).html(_gettext('Loading ...'));
494 492
495 493 $(self.editContainer).hide();
496 494 $(self.previewContainer).show();
497 495
498 496 // by default we reset state of comment preserving the text
499 497 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
500 498 var prefix = "Error while preview of comment.\n"
501 499 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
502 500 ajaxErrorSwal(message);
503 501
504 502 self.resetCommentFormState(text)
505 503 };
506 504 self.submitAjaxPOST(
507 505 self.previewUrl, postData, self.previewSuccessCallback,
508 506 previewFailCallback);
509 507
510 508 $(self.previewButton).parent().addClass('active');
511 509 $(self.editButton).parent().removeClass('active');
512 510 });
513 511
514 512 $(this.submitForm).submit(function(e) {
515 513 e.preventDefault();
516 514 var allowedToSubmit = self.isAllowedToSubmit();
517 515 if (!allowedToSubmit){
518 516 return false;
519 517 }
520 518
521 519 self.handleFormSubmit();
522 520 });
523 521
524 522 }
525 523
526 524 return CommentForm;
527 525 });
528 526
529 527 /* selector for comment versions */
530 528 var initVersionSelector = function(selector, initialData) {
531 529
532 530 var formatResult = function(result, container, query, escapeMarkup) {
533 531
534 532 return renderTemplate('commentVersion', {
535 533 show_disabled: true,
536 534 version: result.comment_version,
537 535 user_name: result.comment_author_username,
538 536 gravatar_url: result.comment_author_gravatar,
539 537 size: 16,
540 538 timeago_component: result.comment_created_on,
541 539 })
542 540 };
543 541
544 542 $(selector).select2({
545 543 placeholder: "Edited",
546 544 containerCssClass: "drop-menu-comment-history",
547 545 dropdownCssClass: "drop-menu-dropdown",
548 546 dropdownAutoWidth: true,
549 547 minimumResultsForSearch: -1,
550 548 data: initialData,
551 549 formatResult: formatResult,
552 550 });
553 551
554 552 $(selector).on('select2-selecting', function (e) {
555 553 // hide the mast as we later do preventDefault()
556 554 $("#select2-drop-mask").click();
557 555 e.preventDefault();
558 556 e.choice.action();
559 557 });
560 558
561 559 $(selector).on("select2-open", function() {
562 560 timeagoActivate();
563 561 });
564 562 };
565 563
566 564 /* comments controller */
567 565 var CommentsController = function() {
568 566 var mainComment = '#text';
569 567 var self = this;
570 568
571 569 this.showVersion = function (comment_id, comment_history_id) {
572 570
573 571 var historyViewUrl = pyroutes.url(
574 572 'repo_commit_comment_history_view',
575 573 {
576 574 'repo_name': templateContext.repo_name,
577 575 'commit_id': comment_id,
578 576 'comment_history_id': comment_history_id,
579 577 }
580 578 );
581 579 successRenderCommit = function (data) {
582 580 SwalNoAnimation.fire({
583 581 html: data,
584 582 title: '',
585 583 });
586 584 };
587 585 failRenderCommit = function () {
588 586 SwalNoAnimation.fire({
589 587 html: 'Error while loading comment history',
590 588 title: '',
591 589 });
592 590 };
593 591 _submitAjaxPOST(
594 592 historyViewUrl, {'csrf_token': CSRF_TOKEN},
595 593 successRenderCommit,
596 594 failRenderCommit
597 595 );
598 596 };
599 597
600 598 this.getLineNumber = function(node) {
601 599 var $node = $(node);
602 600 var lineNo = $node.closest('td').attr('data-line-no');
603 601 if (lineNo === undefined && $node.data('commentInline')){
604 602 lineNo = $node.data('commentLineNo')
605 603 }
606 604
607 605 return lineNo
608 606 };
609 607
610 608 this.scrollToComment = function(node, offset, outdated) {
611 609 if (offset === undefined) {
612 610 offset = 0;
613 611 }
614 612 var outdated = outdated || false;
615 613 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
616 614
617 615 if (!node) {
618 616 node = $('.comment-selected');
619 617 if (!node.length) {
620 618 node = $('comment-current')
621 619 }
622 620 }
623 621
624 622 $wrapper = $(node).closest('div.comment');
625 623
626 624 // show hidden comment when referenced.
627 625 if (!$wrapper.is(':visible')){
628 626 $wrapper.show();
629 627 }
630 628
631 629 $comment = $(node).closest(klass);
632 630 $comments = $(klass);
633 631
634 632 $('.comment-selected').removeClass('comment-selected');
635 633
636 634 var nextIdx = $(klass).index($comment) + offset;
637 635 if (nextIdx >= $comments.length) {
638 636 nextIdx = 0;
639 637 }
640 638 var $next = $(klass).eq(nextIdx);
641 639
642 640 var $cb = $next.closest('.cb');
643 641 $cb.removeClass('cb-collapsed');
644 642
645 643 var $filediffCollapseState = $cb.closest('.filediff').prev();
646 644 $filediffCollapseState.prop('checked', false);
647 645 $next.addClass('comment-selected');
648 646 scrollToElement($next);
649 647 return false;
650 648 };
651 649
652 650 this.nextComment = function(node) {
653 651 return self.scrollToComment(node, 1);
654 652 };
655 653
656 654 this.prevComment = function(node) {
657 655 return self.scrollToComment(node, -1);
658 656 };
659 657
660 658 this.nextOutdatedComment = function(node) {
661 659 return self.scrollToComment(node, 1, true);
662 660 };
663 661
664 662 this.prevOutdatedComment = function(node) {
665 663 return self.scrollToComment(node, -1, true);
666 664 };
667 665
668 666 this.cancelComment = function (node) {
669 667 var $node = $(node);
670 668 var edit = $(this).attr('edit');
671 669 var $inlineComments = $node.closest('div.inline-comments');
672 670
673 671 if (edit) {
674 672 var $general_comments = null;
675 673 if (!$inlineComments.length) {
676 674 $general_comments = $('#comments');
677 675 var $comment = $general_comments.parent().find('div.comment:hidden');
678 676 // show hidden general comment form
679 677 $('#cb-comment-general-form-placeholder').show();
680 678 } else {
681 679 var $comment = $inlineComments.find('div.comment:hidden');
682 680 }
683 681 $comment.show();
684 682 }
685 683 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
686 684 $replyWrapper.removeClass('comment-form-active');
687 685
688 686 var lastComment = $inlineComments.find('.comment-inline').last();
689 687 if ($(lastComment).hasClass('comment-outdated')) {
690 688 $replyWrapper.hide();
691 689 }
692 690
693 691 $node.closest('.comment-inline-form').remove();
694 692 return false;
695 693 };
696 694
697 695 this._deleteComment = function(node) {
698 696 var $node = $(node);
699 697 var $td = $node.closest('td');
700 698 var $comment = $node.closest('.comment');
701 699 var comment_id = $($comment).data('commentId');
702 700 var isDraft = $($comment).data('commentDraft');
703 701
704 702 var pullRequestId = templateContext.pull_request_data.pull_request_id;
705 703 var commitId = templateContext.commit_data.commit_id;
706 704
707 705 if (pullRequestId) {
708 706 var url = pyroutes.url('pullrequest_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
709 707 } else if (commitId) {
710 708 var url = pyroutes.url('repo_commit_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "commit_id": commitId})
711 709 }
712 710
713 711 var postData = {
714 712 'csrf_token': CSRF_TOKEN
715 713 };
716 714
717 715 $comment.addClass('comment-deleting');
718 716 $comment.hide('fast');
719 717
720 718 var success = function(response) {
721 719 $comment.remove();
722 720
723 721 if (window.updateSticky !== undefined) {
724 722 // potentially our comments change the active window size, so we
725 723 // notify sticky elements
726 724 updateSticky()
727 725 }
728 726
729 727 if (window.refreshAllComments !== undefined && !isDraft) {
730 728 // if we have this handler, run it, and refresh all comments boxes
731 729 refreshAllComments()
732 730 }
733 731 else if (window.refreshDraftComments !== undefined && isDraft) {
734 732 // if we have this handler, run it, and refresh all comments boxes
735 733 refreshDraftComments();
736 734 }
737 735 return false;
738 736 };
739 737
740 738 var failure = function(jqXHR, textStatus, errorThrown) {
741 739 var prefix = "Error while deleting this comment.\n"
742 740 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
743 741 ajaxErrorSwal(message);
744 742
745 743 $comment.show('fast');
746 744 $comment.removeClass('comment-deleting');
747 745 return false;
748 746 };
749 747 ajaxPOST(url, postData, success, failure);
750 748
751 749 }
752 750
753 751 this.deleteComment = function(node) {
754 752 var $comment = $(node).closest('.comment');
755 753 var comment_id = $comment.attr('data-comment-id');
756 754
757 755 SwalNoAnimation.fire({
758 756 title: 'Delete this comment?',
759 757 icon: 'warning',
760 758 showCancelButton: true,
761 759 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
762 760
763 761 }).then(function(result) {
764 762 if (result.value) {
765 763 self._deleteComment(node);
766 764 }
767 765 })
768 766 };
769 767
770 768 this._finalizeDrafts = function(commentIds) {
771 769
772 770 var pullRequestId = templateContext.pull_request_data.pull_request_id;
773 771 var commitId = templateContext.commit_data.commit_id;
774 772
775 773 if (pullRequestId) {
776 774 var url = pyroutes.url('pullrequest_draft_comments_submit', {"repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
777 775 } else if (commitId) {
778 776 var url = pyroutes.url('commit_draft_comments_submit', {"repo_name": templateContext.repo_name, "commit_id": commitId})
779 777 }
780 778
781 779 // remove the drafts so we can lock them before submit.
782 780 $.each(commentIds, function(idx, val){
783 781 $('#comment-{0}'.format(val)).remove();
784 782 })
785 783
786 784 var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN};
787 785
788 786 var submitSuccessCallback = function(json_data) {
789 787 self.attachInlineComment(json_data);
790 788
791 789 if (window.refreshDraftComments !== undefined) {
792 790 // if we have this handler, run it, and refresh all comments boxes
793 791 refreshDraftComments()
794 792 }
795 793
796 794 return false;
797 795 };
798 796
799 797 ajaxPOST(url, postData, submitSuccessCallback)
800 798
801 799 }
802 800
803 801 this.finalizeDrafts = function(commentIds, callback) {
804 802
805 803 SwalNoAnimation.fire({
806 804 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
807 805 icon: 'warning',
808 806 showCancelButton: true,
809 807 confirmButtonText: _gettext('Yes'),
810 808
811 809 }).then(function(result) {
812 810 if (result.value) {
813 811 if (callback !== undefined) {
814 812 callback(result)
815 813 }
816 814 self._finalizeDrafts(commentIds);
817 815 }
818 816 })
819 817 };
820 818
821 819 this.toggleWideMode = function (node) {
822 820
823 821 if ($('#content').hasClass('wrapper')) {
824 822 $('#content').removeClass("wrapper");
825 823 $('#content').addClass("wide-mode-wrapper");
826 824 $(node).addClass('btn-success');
827 825 return true
828 826 } else {
829 827 $('#content').removeClass("wide-mode-wrapper");
830 828 $('#content').addClass("wrapper");
831 829 $(node).removeClass('btn-success');
832 830 return false
833 831 }
834 832
835 833 };
836 834
837 835 /**
838 836 * Turn off/on all comments in file diff
839 837 */
840 838 this.toggleDiffComments = function(node) {
841 839 // Find closes filediff container
842 840 var $filediff = $(node).closest('.filediff');
843 841 if ($(node).hasClass('toggle-on')) {
844 842 var show = false;
845 843 } else if ($(node).hasClass('toggle-off')) {
846 844 var show = true;
847 845 }
848 846
849 847 // Toggle each individual comment block, so we can un-toggle single ones
850 848 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
851 849 self.toggleLineComments($(val), show)
852 850 })
853 851
854 852 // since we change the height of the diff container that has anchor points for upper
855 853 // sticky header, we need to tell it to re-calculate those
856 854 if (window.updateSticky !== undefined) {
857 855 // potentially our comments change the active window size, so we
858 856 // notify sticky elements
859 857 updateSticky()
860 858 }
861 859
862 860 return false;
863 861 }
864 862
865 863 this.toggleLineComments = function(node, show) {
866 864
867 865 var trElem = $(node).closest('tr')
868 866
869 867 if (show === true) {
870 868 // mark outdated comments as visible before the toggle;
871 869 $(trElem).find('.comment-outdated').show();
872 870 $(trElem).removeClass('hide-line-comments');
873 871 } else if (show === false) {
874 872 $(trElem).find('.comment-outdated').hide();
875 873 $(trElem).addClass('hide-line-comments');
876 874 } else {
877 875 // mark outdated comments as visible before the toggle;
878 876 $(trElem).find('.comment-outdated').show();
879 877 $(trElem).toggleClass('hide-line-comments');
880 878 }
881 879
882 880 // since we change the height of the diff container that has anchor points for upper
883 881 // sticky header, we need to tell it to re-calculate those
884 882 if (window.updateSticky !== undefined) {
885 883 // potentially our comments change the active window size, so we
886 884 // notify sticky elements
887 885 updateSticky()
888 886 }
889 887
890 888 };
891 889
892 890 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
893 891 var pullRequestId = templateContext.pull_request_data.pull_request_id;
894 892 var commitId = templateContext.commit_data.commit_id;
895 893
896 894 var commentForm = new CommentForm(
897 895 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
898 896 var cm = commentForm.getCmInstance();
899 897
900 898 if (resolvesCommentId){
901 899 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
902 900 }
903 901
904 902 setTimeout(function() {
905 903 // callbacks
906 904 if (cm !== undefined) {
907 905 commentForm.setPlaceholder(placeholderText);
908 906 if (commentForm.isInline()) {
909 907 cm.focus();
910 908 cm.refresh();
911 909 }
912 910 }
913 911 }, 10);
914 912
915 913 // trigger scrolldown to the resolve comment, since it might be away
916 914 // from the clicked
917 915 if (resolvesCommentId){
918 916 var actionNode = $(commentForm.resolvesActionId).offset();
919 917
920 918 setTimeout(function() {
921 919 if (actionNode) {
922 920 $('body, html').animate({scrollTop: actionNode.top}, 10);
923 921 }
924 922 }, 100);
925 923 }
926 924
927 925 // add dropzone support
928 926 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
929 927 var renderer = templateContext.visual.default_renderer;
930 928 if (renderer == 'rst') {
931 929 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
932 930 if (isRendered){
933 931 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
934 932 }
935 933 } else if (renderer == 'markdown') {
936 934 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
937 935 if (isRendered){
938 936 attachmentUrl = '!' + attachmentUrl;
939 937 }
940 938 } else {
941 939 var attachmentUrl = '{}'.format(attachmentStoreUrl);
942 940 }
943 941 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
944 942
945 943 return false;
946 944 };
947 945
948 946 //see: https://www.dropzonejs.com/#configuration
949 947 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
950 948 {'repo_name': templateContext.repo_name,
951 949 'commit_id': templateContext.commit_data.commit_id})
952 950
953 951 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
954 952 if (previewTmpl !== undefined){
955 953 var selectLink = $(formElement).find('.pick-attachment').get(0);
956 954 $(formElement).find('.comment-attachment-uploader').dropzone({
957 955 url: storeUrl,
958 956 headers: {"X-CSRF-Token": CSRF_TOKEN},
959 957 paramName: function () {
960 958 return "attachment"
961 959 }, // The name that will be used to transfer the file
962 960 clickable: selectLink,
963 961 parallelUploads: 1,
964 962 maxFiles: 10,
965 963 maxFilesize: templateContext.attachment_store.max_file_size_mb,
966 964 uploadMultiple: false,
967 965 autoProcessQueue: true, // if false queue will not be processed automatically.
968 966 createImageThumbnails: false,
969 967 previewTemplate: previewTmpl.innerHTML,
970 968
971 969 accept: function (file, done) {
972 970 done();
973 971 },
974 972 init: function () {
975 973
976 974 this.on("sending", function (file, xhr, formData) {
977 975 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
978 976 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
979 977 });
980 978
981 979 this.on("success", function (file, response) {
982 980 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
983 981 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
984 982
985 983 var isRendered = false;
986 984 var ext = file.name.split('.').pop();
987 985 var imageExts = templateContext.attachment_store.image_ext;
988 986 if (imageExts.indexOf(ext) !== -1){
989 987 isRendered = true;
990 988 }
991 989
992 990 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
993 991 });
994 992
995 993 this.on("error", function (file, errorMessage, xhr) {
996 994 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
997 995
998 996 var error = null;
999 997
1000 998 if (xhr !== undefined){
1001 999 var httpStatus = xhr.status + " " + xhr.statusText;
1002 1000 if (xhr !== undefined && xhr.status >= 500) {
1003 1001 error = httpStatus;
1004 1002 }
1005 1003 }
1006 1004
1007 1005 if (error === null) {
1008 1006 error = errorMessage.error || errorMessage || httpStatus;
1009 1007 }
1010 1008 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
1011 1009
1012 1010 });
1013 1011 }
1014 1012 });
1015 1013 }
1016 1014 return commentForm;
1017 1015 };
1018 1016
1019 1017 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
1020 1018
1021 1019 var tmpl = $('#cb-comment-general-form-template').html();
1022 1020 tmpl = tmpl.format(null, 'general');
1023 1021 var $form = $(tmpl);
1024 1022
1025 1023 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
1026 1024 var curForm = $formPlaceholder.find('form');
1027 1025 if (curForm){
1028 1026 curForm.remove();
1029 1027 }
1030 1028 $formPlaceholder.append($form);
1031 1029
1032 1030 var _form = $($form[0]);
1033 1031 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
1034 1032 var edit = false;
1035 1033 var comment_id = null;
1036 1034 var commentForm = this.createCommentForm(
1037 1035 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
1038 1036 commentForm.initStatusChangeSelector();
1039 1037
1040 1038 return commentForm;
1041 1039 };
1042 1040
1043 1041 this.editComment = function(node, line_no, f_path) {
1044 1042 self.edit = true;
1045 1043 var $node = $(node);
1046 1044 var $td = $node.closest('td');
1047 1045
1048 1046 var $comment = $(node).closest('.comment');
1049 1047 var comment_id = $($comment).data('commentId');
1050 1048 var isDraft = $($comment).data('commentDraft');
1051 1049 var $editForm = null
1052 1050
1053 1051 var $comments = $node.closest('div.inline-comments');
1054 1052 var $general_comments = null;
1055 1053
1056 1054 if($comments.length){
1057 1055 // inline comments setup
1058 1056 $editForm = $comments.find('.comment-inline-form');
1059 1057 line_no = self.getLineNumber(node)
1060 1058 }
1061 1059 else{
1062 1060 // general comments setup
1063 1061 $comments = $('#comments');
1064 1062 $editForm = $comments.find('.comment-inline-form');
1065 1063 line_no = $comment[0].id
1066 1064 $('#cb-comment-general-form-placeholder').hide();
1067 1065 }
1068 1066
1069 1067 if ($editForm.length === 0) {
1070 1068
1071 1069 // unhide all comments if they are hidden for a proper REPLY mode
1072 1070 var $filediff = $node.closest('.filediff');
1073 1071 $filediff.removeClass('hide-comments');
1074 1072
1075 1073 $editForm = self.createNewFormWrapper(f_path, line_no);
1076 1074 if(f_path && line_no) {
1077 1075 $editForm.addClass('comment-inline-form-edit')
1078 1076 }
1079 1077
1080 1078 $comment.after($editForm)
1081 1079
1082 1080 var _form = $($editForm[0]).find('form');
1083 1081 var autocompleteActions = ['as_note',];
1084 1082 var commentForm = this.createCommentForm(
1085 1083 _form, line_no, '', autocompleteActions, resolvesCommentId,
1086 1084 this.edit, comment_id);
1087 1085 var old_comment_text_binary = $comment.attr('data-comment-text');
1088 1086 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1089 1087 commentForm.cm.setValue(old_comment_text);
1090 1088 $comment.hide();
1091 1089 tooltipActivate();
1092 1090
1093 1091 // set a CUSTOM submit handler for inline comment edit action.
1094 1092 commentForm.setHandleFormSubmit(function(o) {
1095 1093 var text = commentForm.cm.getValue();
1096 1094 var commentType = commentForm.getCommentType();
1097 1095
1098 1096 if (text === "") {
1099 1097 return;
1100 1098 }
1101 1099
1102 1100 if (old_comment_text == text) {
1103 1101 SwalNoAnimation.fire({
1104 1102 title: 'Unable to edit comment',
1105 1103 html: _gettext('Comment body was not changed.'),
1106 1104 });
1107 1105 return;
1108 1106 }
1109 1107 var excludeCancelBtn = false;
1110 1108 var submitEvent = true;
1111 1109 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1112 1110 commentForm.cm.setOption("readOnly", true);
1113 1111
1114 1112 // Read last version known
1115 1113 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
1116 1114 var version = versionSelector.data('lastVersion');
1117 1115
1118 1116 if (!version) {
1119 1117 version = 0;
1120 1118 }
1121 1119
1122 1120 var postData = {
1123 1121 'text': text,
1124 1122 'f_path': f_path,
1125 1123 'line': line_no,
1126 1124 'comment_type': commentType,
1127 1125 'draft': isDraft,
1128 1126 'version': version,
1129 1127 'csrf_token': CSRF_TOKEN
1130 1128 };
1131 1129
1132 1130 var submitSuccessCallback = function(json_data) {
1133 1131 $editForm.remove();
1134 1132 $comment.show();
1135 1133 var postData = {
1136 1134 'text': text,
1137 1135 'renderer': $comment.attr('data-comment-renderer'),
1138 1136 'csrf_token': CSRF_TOKEN
1139 1137 };
1140 1138
1141 1139 /* Inject new edited version selector */
1142 1140 var updateCommentVersionDropDown = function () {
1143 1141 var versionSelectId = '#comment_versions_'+comment_id;
1144 1142 var preLoadVersionData = [
1145 1143 {
1146 1144 id: json_data['comment_version'],
1147 1145 text: "v{0}".format(json_data['comment_version']),
1148 1146 action: function () {
1149 1147 Rhodecode.comments.showVersion(
1150 1148 json_data['comment_id'],
1151 1149 json_data['comment_history_id']
1152 1150 )
1153 1151 },
1154 1152 comment_version: json_data['comment_version'],
1155 1153 comment_author_username: json_data['comment_author_username'],
1156 1154 comment_author_gravatar: json_data['comment_author_gravatar'],
1157 1155 comment_created_on: json_data['comment_created_on'],
1158 1156 },
1159 1157 ]
1160 1158
1161 1159
1162 1160 if ($(versionSelectId).data('select2')) {
1163 1161 var oldData = $(versionSelectId).data('select2').opts.data.results;
1164 1162 $(versionSelectId).select2("destroy");
1165 1163 preLoadVersionData = oldData.concat(preLoadVersionData)
1166 1164 }
1167 1165
1168 1166 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1169 1167
1170 1168 $comment.attr('data-comment-text', utf8ToB64(text));
1171 1169
1172 1170 var versionSelector = $('#comment_versions_'+comment_id);
1173 1171
1174 1172 // set lastVersion so we know our last edit version
1175 1173 versionSelector.data('lastVersion', json_data['comment_version'])
1176 1174 versionSelector.parent().show();
1177 1175 }
1178 1176 updateCommentVersionDropDown();
1179 1177
1180 1178 // by default we reset state of comment preserving the text
1181 1179 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1182 1180 var prefix = "Error while editing this comment.\n"
1183 1181 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1184 1182 ajaxErrorSwal(message);
1185 1183 };
1186 1184
1187 1185 var successRenderCommit = function(o){
1188 1186 $comment.show();
1189 1187 $comment[0].lastElementChild.innerHTML = o;
1190 1188 };
1191 1189
1192 1190 var previewUrl = pyroutes.url(
1193 1191 'repo_commit_comment_preview',
1194 1192 {'repo_name': templateContext.repo_name,
1195 1193 'commit_id': templateContext.commit_data.commit_id});
1196 1194
1197 1195 _submitAjaxPOST(
1198 1196 previewUrl, postData, successRenderCommit, failRenderCommit
1199 1197 );
1200 1198
1201 1199 try {
1202 1200 var html = json_data.rendered_text;
1203 1201 var lineno = json_data.line_no;
1204 1202 var target_id = json_data.target_id;
1205 1203
1206 1204 $comments.find('.cb-comment-add-button').before(html);
1207 1205
1208 1206 // run global callback on submit
1209 1207 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1210 1208
1211 1209 } catch (e) {
1212 1210 console.error(e);
1213 1211 }
1214 1212
1215 1213 // re trigger the linkification of next/prev navigation
1216 1214 linkifyComments($('.inline-comment-injected'));
1217 1215 timeagoActivate();
1218 1216 tooltipActivate();
1219 1217
1220 1218 if (window.updateSticky !== undefined) {
1221 1219 // potentially our comments change the active window size, so we
1222 1220 // notify sticky elements
1223 1221 updateSticky()
1224 1222 }
1225 1223
1226 1224 if (window.refreshAllComments !== undefined && !isDraft) {
1227 1225 // if we have this handler, run it, and refresh all comments boxes
1228 1226 refreshAllComments()
1229 1227 }
1230 1228 else if (window.refreshDraftComments !== undefined && isDraft) {
1231 1229 // if we have this handler, run it, and refresh all comments boxes
1232 1230 refreshDraftComments();
1233 1231 }
1234 1232
1235 1233 commentForm.setActionButtonsDisabled(false);
1236 1234
1237 1235 };
1238 1236
1239 1237 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1240 1238 var prefix = "Error while editing comment.\n"
1241 1239 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1242 1240 if (jqXHR.status == 409){
1243 1241 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1244 1242 ajaxErrorSwal(message, 'Comment version mismatch.');
1245 1243 } else {
1246 1244 ajaxErrorSwal(message);
1247 1245 }
1248 1246
1249 1247 commentForm.resetCommentFormState(text)
1250 1248 };
1251 1249 commentForm.submitAjaxPOST(
1252 1250 commentForm.submitUrl, postData,
1253 1251 submitSuccessCallback,
1254 1252 submitFailCallback);
1255 1253 });
1256 1254 }
1257 1255
1258 1256 $editForm.addClass('comment-inline-form-open');
1259 1257 };
1260 1258
1261 1259 this.attachComment = function(json_data) {
1262 1260 var self = this;
1263 1261 $.each(json_data, function(idx, val) {
1264 1262 var json_data_elem = [val]
1265 1263 var isInline = val.comment_f_path && val.comment_lineno
1266 1264
1267 1265 if (isInline) {
1268 1266 self.attachInlineComment(json_data_elem)
1269 1267 } else {
1270 1268 self.attachGeneralComment(json_data_elem)
1271 1269 }
1272 1270 })
1273 1271
1274 1272 }
1275 1273
1276 1274 this.attachGeneralComment = function(json_data) {
1277 1275 $.each(json_data, function(idx, val) {
1278 1276 $('#injected_page_comments').append(val.rendered_text);
1279 1277 })
1280 1278 }
1281 1279
1282 1280 this.attachInlineComment = function(json_data) {
1283 1281
1284 1282 $.each(json_data, function (idx, val) {
1285 1283 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1286 1284 var html = val.rendered_text;
1287 1285 var $inlineComments = $('#' + val.target_id)
1288 1286 .find(line_qry)
1289 1287 .find('.inline-comments');
1290 1288
1291 1289 var lastComment = $inlineComments.find('.comment-inline').last();
1292 1290
1293 1291 if (lastComment.length === 0) {
1294 1292 // first comment, we append simply
1295 1293 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1296 1294 } else {
1297 1295 $(lastComment).after(html)
1298 1296 }
1299 1297
1300 1298 })
1301 1299
1302 1300 };
1303 1301
1304 1302 this.createNewFormWrapper = function(f_path, line_no) {
1305 1303 // create a new reply HTML form from template
1306 1304 var tmpl = $('#cb-comment-inline-form-template').html();
1307 1305 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1308 1306 return $(tmpl);
1309 1307 }
1310 1308
1309 this.markCommentResolved = function(commentId) {
1310 $('#comment-label-{0}'.format(commentId)).find('.resolved').show();
1311 $('#comment-label-{0}'.format(commentId)).find('.resolve').hide();
1312 };
1313
1311 1314 this.createComment = function(node, f_path, line_no, resolutionComment) {
1312 1315 self.edit = false;
1313 1316 var $node = $(node);
1314 1317 var $td = $node.closest('td');
1315 1318 var resolvesCommentId = resolutionComment || null;
1316 1319
1317 1320 var $replyForm = $td.find('.comment-inline-form');
1318 1321
1319 1322 // if form isn't existing, we're generating a new one and injecting it.
1320 1323 if ($replyForm.length === 0) {
1321 1324
1322 1325 // unhide/expand all comments if they are hidden for a proper REPLY mode
1323 1326 self.toggleLineComments($node, true);
1324 1327
1325 1328 $replyForm = self.createNewFormWrapper(f_path, line_no);
1326 1329
1327 1330 var $comments = $td.find('.inline-comments');
1328 1331
1329 1332 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1330 1333 if ($comments.length===0) {
1331 1334 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no)
1332 1335 var $reply_container = $('#cb-comments-inline-container-template')
1333 1336 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1334 1337 $td.append($($reply_container).html());
1335 1338 }
1336 1339
1337 1340 // default comment button exists, so we prepend the form for leaving initial comment
1338 1341 $td.find('.cb-comment-add-button').before($replyForm);
1339 1342 // set marker, that we have a open form
1340 1343 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1341 1344 $replyWrapper.addClass('comment-form-active');
1342 1345
1343 1346 var lastComment = $comments.find('.comment-inline').last();
1344 1347 if ($(lastComment).hasClass('comment-outdated')) {
1345 1348 $replyWrapper.show();
1346 1349 }
1347 1350
1348 1351 var _form = $($replyForm[0]).find('form');
1349 1352 var autocompleteActions = ['as_note', 'as_todo'];
1350 1353 var comment_id=null;
1351 1354 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1352 1355 var commentForm = self.createCommentForm(
1353 1356 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1354 1357 self.edit, comment_id);
1355 1358
1356 1359 // set a CUSTOM submit handler for inline comments.
1357 1360 commentForm.setHandleFormSubmit(function(o) {
1358 1361 var text = commentForm.cm.getValue();
1359 1362 var commentType = commentForm.getCommentType();
1360 1363 var resolvesCommentId = commentForm.getResolvesId();
1361 1364 var isDraft = commentForm.getDraftState();
1362 1365
1363 1366 if (text === "") {
1364 1367 return;
1365 1368 }
1366 1369
1367 1370 if (line_no === undefined) {
1368 1371 alert('Error: unable to fetch line number for this inline comment !');
1369 1372 return;
1370 1373 }
1371 1374
1372 1375 if (f_path === undefined) {
1373 1376 alert('Error: unable to fetch file path for this inline comment !');
1374 1377 return;
1375 1378 }
1376 1379
1377 1380 var excludeCancelBtn = false;
1378 1381 var submitEvent = true;
1379 1382 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1380 1383 commentForm.cm.setOption("readOnly", true);
1381 1384 var postData = {
1382 1385 'text': text,
1383 1386 'f_path': f_path,
1384 1387 'line': line_no,
1385 1388 'comment_type': commentType,
1386 1389 'draft': isDraft,
1387 1390 'csrf_token': CSRF_TOKEN
1388 1391 };
1389 1392 if (resolvesCommentId){
1390 1393 postData['resolves_comment_id'] = resolvesCommentId;
1391 1394 }
1392 1395
1393 1396 // submitSuccess for inline commits
1394 1397 var submitSuccessCallback = function(json_data) {
1395 1398
1396 1399 $replyForm.remove();
1397 1400 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1398 1401
1399 1402 try {
1400 1403
1401 1404 // inject newly created comments, json_data is {<comment_id>: {}}
1402 1405 self.attachInlineComment(json_data)
1403 1406
1404 1407 //mark visually which comment was resolved
1405 1408 if (resolvesCommentId) {
1406 commentForm.markCommentResolved(resolvesCommentId);
1409 self.markCommentResolved(resolvesCommentId);
1407 1410 }
1408 1411
1409 1412 // run global callback on submit
1410 1413 commentForm.globalSubmitSuccessCallback({
1411 1414 draft: isDraft,
1412 1415 comment_id: comment_id
1413 1416 });
1414 1417
1415 1418 } catch (e) {
1416 1419 console.error(e);
1417 1420 }
1418 1421
1419 1422 if (window.updateSticky !== undefined) {
1420 1423 // potentially our comments change the active window size, so we
1421 1424 // notify sticky elements
1422 1425 updateSticky()
1423 1426 }
1424 1427
1425 1428 if (window.refreshAllComments !== undefined && !isDraft) {
1426 1429 // if we have this handler, run it, and refresh all comments boxes
1427 1430 refreshAllComments()
1428 1431 }
1429 1432 else if (window.refreshDraftComments !== undefined && isDraft) {
1430 1433 // if we have this handler, run it, and refresh all comments boxes
1431 1434 refreshDraftComments();
1432 1435 }
1433 1436
1434 1437 commentForm.setActionButtonsDisabled(false);
1435 1438
1436 1439 // re trigger the linkification of next/prev navigation
1437 1440 linkifyComments($('.inline-comment-injected'));
1438 1441 timeagoActivate();
1439 1442 tooltipActivate();
1440 1443 };
1441 1444
1442 1445 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1443 1446 var prefix = "Error while submitting comment.\n"
1444 1447 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1445 1448 ajaxErrorSwal(message);
1446 1449 commentForm.resetCommentFormState(text)
1447 1450 };
1448 1451
1449 1452 commentForm.submitAjaxPOST(
1450 1453 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1451 1454 });
1452 1455 }
1453 1456
1454 1457 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1455 1458 $replyForm.addClass('comment-inline-form-open');
1456 1459 tooltipActivate();
1457 1460 };
1458 1461
1459 1462 this.createResolutionComment = function(commentId){
1460 1463 // hide the trigger text
1461 1464 $('#resolve-comment-{0}'.format(commentId)).hide();
1462 1465
1463 1466 var comment = $('#comment-'+commentId);
1464 1467 var commentData = comment.data();
1465 console.log(commentData);
1466 1468
1467 1469 if (commentData.commentInline) {
1468 1470 var f_path = commentData.commentFPath;
1469 1471 var line_no = commentData.commentLineNo;
1470 1472 this.createComment(comment, f_path, line_no, commentId)
1471 1473 } else {
1472 1474 this.createGeneralComment('general', "$placeholder", commentId)
1473 1475 }
1474 1476
1475 1477 return false;
1476 1478 };
1477 1479
1478 1480 this.submitResolution = function(commentId){
1479 1481 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1480 1482 var commentForm = form.get(0).CommentForm;
1481 1483
1482 1484 var cm = commentForm.getCmInstance();
1483 1485 var renderer = templateContext.visual.default_renderer;
1484 1486 if (renderer == 'rst'){
1485 1487 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1486 1488 } else if (renderer == 'markdown') {
1487 1489 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1488 1490 } else {
1489 1491 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1490 1492 }
1491 1493
1492 1494 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1493 1495 form.submit();
1494 1496 return false;
1495 1497 };
1496 1498
1499 this.resolveTodo = function (elem, todoId) {
1500 var commentId = todoId;
1501
1502 SwalNoAnimation.fire({
1503 title: 'Resolve TODO {0}'.format(todoId),
1504 showCancelButton: true,
1505 confirmButtonText: _gettext('Yes'),
1506 showLoaderOnConfirm: true,
1507
1508 allowOutsideClick: function () {
1509 !Swal.isLoading()
1510 },
1511 preConfirm: function () {
1512 var comment = $('#comment-' + commentId);
1513 var commentData = comment.data();
1514
1515 var f_path = null
1516 var line_no = null
1517 if (commentData.commentInline) {
1518 f_path = commentData.commentFPath;
1519 line_no = commentData.commentLineNo;
1520 }
1521
1522 var renderer = templateContext.visual.default_renderer;
1523 var commentBoxUrl = '{1}#comment-{0}'.format(commentId);
1524
1525 // Pull request case
1526 if (templateContext.pull_request_data.pull_request_id !== null) {
1527 var commentUrl = pyroutes.url('pullrequest_comment_create',
1528 {
1529 'repo_name': templateContext.repo_name,
1530 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1531 'comment_id': commentId
1532 });
1533 } else {
1534 var commentUrl = pyroutes.url('repo_commit_comment_create',
1535 {
1536 'repo_name': templateContext.repo_name,
1537 'commit_id': templateContext.commit_data.commit_id,
1538 'comment_id': commentId
1539 });
1540 }
1541
1542 if (renderer === 'rst') {
1543 commentBoxUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentUrl);
1544 } else if (renderer === 'markdown') {
1545 commentBoxUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentUrl);
1546 }
1547 var resolveText = _gettext('TODO from comment {0} was fixed.').format(commentBoxUrl);
1548
1549 var postData = {
1550 text: resolveText,
1551 comment_type: 'note',
1552 draft: false,
1553 csrf_token: CSRF_TOKEN,
1554 resolves_comment_id: commentId
1555 }
1556 if (commentData.commentInline) {
1557 postData['f_path'] = f_path;
1558 postData['line'] = line_no;
1559 }
1560
1561 return new Promise(function (resolve, reject) {
1562 $.ajax({
1563 type: 'POST',
1564 data: postData,
1565 url: commentUrl,
1566 headers: {'X-PARTIAL-XHR': true}
1567 })
1568 .done(function (data) {
1569 resolve(data);
1570 })
1571 .fail(function (jqXHR, textStatus, errorThrown) {
1572 var prefix = "Error while resolving TODO.\n"
1573 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1574 ajaxErrorSwal(message);
1575 });
1576 })
1577 }
1578
1579 })
1580 .then(function (result) {
1581 var success = function (json_data) {
1582 resolvesCommentId = commentId;
1583 var commentResolved = json_data[Object.keys(json_data)[0]]
1584
1585 try {
1586
1587 if (commentResolved.f_path) {
1588 // inject newly created comments, json_data is {<comment_id>: {}}
1589 self.attachInlineComment(json_data)
1590 } else {
1591 self.attachGeneralComment(json_data)
1592 }
1593
1594 //mark visually which comment was resolved
1595 if (resolvesCommentId) {
1596 self.markCommentResolved(resolvesCommentId);
1597 }
1598
1599 // run global callback on submit
1600 if (window.commentFormGlobalSubmitSuccessCallback !== undefined) {
1601 commentFormGlobalSubmitSuccessCallback({
1602 draft: false,
1603 comment_id: commentId
1604 });
1605 }
1606
1607 } catch (e) {
1608 console.error(e);
1609 }
1610
1611 if (window.updateSticky !== undefined) {
1612 // potentially our comments change the active window size, so we
1613 // notify sticky elements
1614 updateSticky()
1615 }
1616
1617 if (window.refreshAllComments !== undefined) {
1618 // if we have this handler, run it, and refresh all comments boxes
1619 refreshAllComments()
1620 }
1621 // re trigger the linkification of next/prev navigation
1622 linkifyComments($('.inline-comment-injected'));
1623 timeagoActivate();
1624 tooltipActivate();
1625 };
1626
1627 if (result.value) {
1628 $(elem).remove();
1629 success(result.value)
1630 }
1631 })
1632 };
1633
1497 1634 };
1498 1635
1499 1636 window.commentHelp = function(renderer) {
1500 1637 var funcData = {'renderer': renderer}
1501 1638 return renderTemplate('commentHelpHovercard', funcData)
1502 } No newline at end of file
1639 }
@@ -1,230 +1,233 b''
1 1 ${h.secure_form(h.route_path('admin_settings_visual_update'), request=request)}
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading" id="general">
5 5 <h3 class="panel-title">${_('General')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="checkbox">
9 9 ${h.checkbox('rhodecode_repository_fields','True')}
10 10 <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
11 11 </div>
12 12 <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
13 13
14 14 <div></div>
15 15 <div class="checkbox">
16 16 ${h.checkbox('rhodecode_show_version','True')}
17 17 <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
18 18 </div>
19 19 <span class="help-block">${_('Shows or hides a version number of RhodeCode displayed in the footer.')}</span>
20 20 </div>
21 21 </div>
22 22
23 23
24 24 <div class="panel panel-default">
25 25 <div class="panel-heading" id="gravatars">
26 26 <h3 class="panel-title">${_('Gravatars')}</h3>
27 27 </div>
28 28 <div class="panel-body">
29 29 <div class="checkbox">
30 30 ${h.checkbox('rhodecode_use_gravatar','True')}
31 31 <label for="rhodecode_use_gravatar">${_('Use Gravatars based avatars')}</label>
32 32 </div>
33 33 <span class="help-block">${_('Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email.')}</span>
34 34
35 35 <div class="label">
36 36 <label for="rhodecode_gravatar_url">${_('Gravatar URL')}</label>
37 37 </div>
38 38 <div class="input">
39 39 <div class="field">
40 40 ${h.text('rhodecode_gravatar_url', size='100%')}
41 41 </div>
42 42
43 43 <div class="field">
44 44 <span class="help-block">${_('''Gravatar url allows you to use other avatar server application.
45 45 Following variables of the URL will be replaced accordingly.
46 46 {scheme} 'http' or 'https' sent from running RhodeCode server,
47 47 {email} user email,
48 48 {md5email} md5 hash of the user email (like at gravatar.com),
49 49 {size} size of the image that is expected from the server application,
50 50 {netloc} network location/server host of running RhodeCode server''')}</span>
51 51 </div>
52 52 </div>
53 53 </div>
54 54 </div>
55 55
56 56
57 57 <div class="panel panel-default">
58 58 <div class="panel-heading" id="meta-tagging">
59 59 <h3 class="panel-title">${_('Meta-Tagging')}</h3>
60 60 </div>
61 61 <div class="panel-body">
62 62 <div class="checkbox">
63 63 ${h.checkbox('rhodecode_stylify_metatags','True')}
64 64 <label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags')}</label>
65 65 </div>
66 66 <span class="help-block">${_('Parses meta tags from repository or repository group description fields and turns them into colored tags.')}</span>
67 67 <div>
68 68 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
69 69 ${dt.metatags_help()}
70 70 </div>
71 71 </div>
72 72 </div>
73 73
74 74
75 75 <div class="panel panel-default">
76 76 <div class="panel-heading">
77 77 <h3 class="panel-title">${_('Dashboard Items')}</h3>
78 78 </div>
79 79 <div class="panel-body">
80 80 <div class="label">
81 81 <label for="rhodecode_dashboard_items">${_('Main page dashboard items')}</label>
82 82 </div>
83 83 <div class="field input">
84 84 ${h.text('rhodecode_dashboard_items',size=5)}
85 85 </div>
86 86 <div class="field">
87 87 <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
88 88 </div>
89 89
90 90 <div class="label">
91 91 <label for="rhodecode_admin_grid_items">${_('Admin pages items')}</label>
92 92 </div>
93 93 <div class="field input">
94 94 ${h.text('rhodecode_admin_grid_items',size=5)}
95 95 </div>
96 96 <div class="field">
97 97 <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
98 98 </div>
99 99 </div>
100 100 </div>
101 101
102 102
103 103
104 104 <div class="panel panel-default">
105 105 <div class="panel-heading" id="commit-id">
106 106 <h3 class="panel-title">${_('Commit ID Style')}</h3>
107 107 </div>
108 108 <div class="panel-body">
109 109 <div class="label">
110 110 <label for="rhodecode_show_sha_length">${_('Commit sha length')}</label>
111 111 </div>
112 112 <div class="input">
113 113 <div class="field">
114 114 ${h.text('rhodecode_show_sha_length',size=5)}
115 115 </div>
116 116 <div class="field">
117 117 <span class="help-block">${_('''Number of chars to show in commit sha displayed in web interface.
118 118 By default it's shown as r123:9043a6a4c226 this value defines the
119 119 length of the sha after the `r123:` part.''')}</span>
120 120 </div>
121 121 </div>
122 122
123 123 <div class="checkbox">
124 124 ${h.checkbox('rhodecode_show_revision_number','True')}
125 125 <label for="rhodecode_show_revision_number">${_('Show commit ID numeric reference')} / ${_('Commit show revision number')}</label>
126 126 </div>
127 127 <span class="help-block">${_('''Show revision number in commit sha displayed in web interface.
128 128 By default it's shown as r123:9043a6a4c226 this value defines the
129 129 if the `r123:` part is shown.''')}</span>
130 130 </div>
131 131 </div>
132 132
133 133
134 134 <div class="panel panel-default">
135 135 <div class="panel-heading" id="icons">
136 136 <h3 class="panel-title">${_('Icons')}</h3>
137 137 </div>
138 138 <div class="panel-body">
139 139 <div class="checkbox">
140 140 ${h.checkbox('rhodecode_show_public_icon','True')}
141 141 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
142 142 </div>
143 143 <div></div>
144 144
145 145 <div class="checkbox">
146 146 ${h.checkbox('rhodecode_show_private_icon','True')}
147 147 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
148 148 </div>
149 149 <span class="help-block">${_('Show public/private icons next to repositories names.')}</span>
150 150 </div>
151 151 </div>
152 152
153 153
154 154 <div class="panel panel-default">
155 155 <div class="panel-heading">
156 156 <h3 class="panel-title">${_('Markup Renderer')}</h3>
157 157 </div>
158 158 <div class="panel-body">
159 159 <div class="field select">
160 160 ${h.select('rhodecode_markup_renderer', '', ['rst', 'markdown'])}
161 161 </div>
162 162 <div class="field">
163 163 <span class="help-block">${_('Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly.')}</span>
164 164 </div>
165 165 </div>
166 166 </div>
167 167
168 168 <div class="panel panel-default">
169 169 <div class="panel-heading">
170 170 <h3 class="panel-title">${_('Clone URL templates')}</h3>
171 171 </div>
172 172 <div class="panel-body">
173 173 <div class="field">
174 174 ${h.text('rhodecode_clone_uri_tmpl', size=60)} HTTP[S]
175 175 </div>
176 176 <div class="field">
177 ${h.text('rhodecode_clone_uri_id_tmpl', size=60)} HTTP UID
178 </div>
179 <div class="field">
177 180 ${h.text('rhodecode_clone_uri_ssh_tmpl', size=60)} SSH
178 181 </div>
179 182 <div class="field">
180 183 <span class="help-block">
181 184 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
182 185 {scheme} 'http' or 'https' sent from running RhodeCode server,
183 186 {user} current user username,
184 187 {sys_user} current system user running this process, Useful for ssh,
185 188 {hostname} hostname of this server running RhodeCode,
186 189 {netloc} network location/server host of running RhodeCode server,
187 190 {repo} full repository name,
188 191 {repoid} ID of repository, can be used to contruct clone-by-id''')}
189 192 </span>
190 193 </div>
191 194 </div>
192 195 </div>
193 196
194 197 <div class="panel panel-default">
195 198 <div class="panel-heading">
196 199 <h3 class="panel-title">${_('Custom Support Link')}</h3>
197 200 </div>
198 201 <div class="panel-body">
199 202 <div class="field">
200 203 ${h.text('rhodecode_support_url', size=60)}
201 204 </div>
202 205 <div class="field">
203 206 <span class="help-block">
204 207 ${_('''Custom url for the support link located at the bottom.
205 208 The default is set to %(default_url)s. In case there's a need
206 209 to change the support link to internal issue tracker, it should be done here.
207 210 ''') % {'default_url': h.route_url('rhodecode_support')}}
208 211 </span>
209 212 </div>
210 213 </div>
211 214 </div>
212 215
213 216 <div class="buttons">
214 217 ${h.submit('save',_('Save settings'),class_="btn")}
215 218 ${h.reset('reset',_('Reset'),class_="btn")}
216 219 </div>
217 220
218 221
219 222 ${h.end_form()}
220 223
221 224 <script>
222 225 $(document).ready(function() {
223 226 $('#rhodecode_markup_renderer').select2({
224 227 containerCssClass: 'drop-menu',
225 228 dropdownCssClass: 'drop-menu-dropdown',
226 229 dropdownAutoWidth: true,
227 230 minimumResultsForSearch: -1
228 231 });
229 232 });
230 233 </script>
@@ -1,270 +1,278 b''
1 1 <%text>
2 2 <div style="display: none">
3 3
4 4 <script>
5 5 var CG = new ColorGenerator();
6 6 </script>
7 7
8 8 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
9 9
10 10 <%
11 11 if (size > 16) {
12 12 var gravatar_class = 'gravatar gravatar-large';
13 13 } else {
14 14 var gravatar_class = 'gravatar';
15 15 }
16 16
17 17 if (tooltip) {
18 18 var gravatar_class = gravatar_class + ' tooltip-hovercard';
19 19 }
20 20
21 21 var data_hovercard_alt = username;
22 22
23 23 %>
24 24
25 25 <%
26 26 if (show_disabled) {
27 27 var user_cls = 'user user-disabled';
28 28 } else {
29 29 var user_cls = 'user';
30 30 }
31 31 var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
32 32 %>
33 33
34 34 <div class="rc-user">
35 35 <img class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" data-hovercard-url="<%= data_hovercard_url %>" data-hovercard-alt="<%= data_hovercard_alt %>" src="<%- gravatar_url -%>">
36 36 <span class="<%= user_cls %>"> <%- user_link -%> </span>
37 37 </div>
38 38
39 39 </script>
40 40
41 41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
42 42 <%
43 43 if (create) {
44 44 var edit_visibility = 'visible';
45 45 } else {
46 46 var edit_visibility = 'hidden';
47 47 }
48 48
49 49 if (member.user_group && member.user_group.vote_rule) {
50 50 var reviewGroup = '<i class="icon-user-group"></i>';
51 51 var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule));
52 52 } else {
53 53 var reviewGroup = null;
54 54 var reviewGroupColor = 'transparent';
55 55 }
56 56 var rule_show = rule_show || false;
57 57
58 58 if (rule_show) {
59 59 var rule_visibility = 'table-cell';
60 60 } else {
61 61 var rule_visibility = 'none';
62 62 }
63 63
64 64 %>
65 65
66 66 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
67 67
68 68 <% if (create) { %>
69 69 <td style="width: 1px"></td>
70 70 <% } else { %>
71 71 <td style="width: 20px">
72 72 <div class="tooltip presence-state" style="display: none; position: absolute; left: 2px" title="This users is currently at this page">
73 73 <i class="icon-eye" style="color: #0ac878"></i>
74 74 </div>
75 75 <% if (role === 'reviewer') { %>
76 76 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
77 77 <i class="icon-circle review-status-<%= review_status %>"></i>
78 78 </div>
79 79 <% } else if (role === 'observer') { %>
80 80 <div class="tooltip" title="Observer without voting right.">
81 81 <i class="icon-circle-thin"></i>
82 82 </div>
83 83 <% } %>
84 84 </td>
85 85 <% } %>
86 86
87 87
88 88 <% if (mandatory) { %>
89 89 <td style="text-align: right;width: 10px;">
90 90 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
91 91 <i class="icon-lock"></i>
92 92 </div>
93 93 </td>
94 94
95 95 <% } else { %>
96 96 <td style="text-align: right;width: 10px;">
97 97 <% if (allowed_to_update) { %>
98 98 <div class="<%=role %>_member_remove" onclick="reviewersController.removeMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
99 99 <i class="icon-remove" style="color: #e85e4d;"></i>
100 100 </div>
101 101 <% } %>
102 102 </td>
103 103 <% } %>
104 104
105 105 <td>
106 106 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
107 107 <%-
108 108 renderTemplate('gravatarWithUser', {
109 109 'size': 16,
110 110 'show_disabled': false,
111 111 'tooltip': true,
112 112 'username': member.username,
113 113 'user_id': member.user_id,
114 114 'user_link': member.user_link,
115 115 'gravatar_url': member.gravatar_link
116 116 })
117 117 %>
118 118 </div>
119 119 <% if (reviewGroup !== null) { %>
120 120 <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>">
121 121 <%- reviewGroup %>
122 122 </span>
123 123 <% } %>
124 124 </td>
125 125
126 126 </tr>
127 127
128 128 <tr id="reviewer_<%= member.user_id %>_rules">
129 129 <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
130 130 <input type="hidden" name="__start__" value="reviewer:mapping">
131 131
132 132 <%if (member.user_group && member.user_group.vote_rule) { %>
133 133 <div class="reviewer_reason">
134 134
135 135 <%if (member.user_group.vote_rule == -1) {%>
136 136 - group votes required: ALL
137 137 <%} else {%>
138 138 - group votes required: <%= member.user_group.vote_rule %>
139 139 <%}%>
140 140 </div>
141 141 <%} %>
142 142
143 143 <input type="hidden" name="__start__" value="reasons:sequence">
144 144 <% for (var i = 0; i < reasons.length; i++) { %>
145 145 <% var reason = reasons[i] %>
146 146 <div class="reviewer_reason">- <%= reason %></div>
147 147 <input type="hidden" name="reason" value="<%= reason %>">
148 148 <% } %>
149 149 <input type="hidden" name="__end__" value="reasons:sequence">
150 150
151 151 <input type="hidden" name="__start__" value="rules:sequence">
152 152 <% for (var i = 0; i < member.rules.length; i++) { %>
153 153 <% var rule = member.rules[i] %>
154 154 <input type="hidden" name="rule_id" value="<%= rule %>">
155 155 <% } %>
156 156 <input type="hidden" name="__end__" value="rules:sequence">
157 157
158 158 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
159 159 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
160 160 <input type="hidden" name="role" value="<%= role %>"/>
161 161
162 162 <input type="hidden" name="__end__" value="reviewer:mapping">
163 163 </td>
164 164 </tr>
165 165
166 166 </script>
167 167
168 168 <script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
169 169
170 170 <%
171 171 if (size > 16) {
172 172 var gravatar_class = 'gravatar gravatar-large';
173 173 } else {
174 174 var gravatar_class = 'gravatar';
175 175 }
176 176
177 177 %>
178 178
179 179 <%
180 180 if (show_disabled) {
181 181 var user_cls = 'user user-disabled';
182 182 } else {
183 183 var user_cls = 'user';
184 184 }
185 185
186 186 %>
187 187
188 188 <div style='line-height: 20px'>
189 189 <img style="margin: -3px 0" class="<%= gravatar_class %>" height="<%= size %>" width="<%= size %>" src="<%- gravatar_url -%>">
190 190 <strong><%- user_name -%></strong>, <code>v<%- version -%></code> edited <%- timeago_component -%>
191 191 </div>
192 192
193 193 </script>
194 194
195 195
196 196 <script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate">
197 197
198 198 <div>
199 199
200 200 <% if (is_todo) { %>
201 201 <% if (inline) { %>
202 202 <strong>Inline</strong> TODO (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
203 203 <% if (version_info) { %>
204 204 <%= version_info %>
205 205 <% } %>
206 206 <br/>
207 207 File: <code><%- file_name -%></code>
208 208 <% } else { %>
209 209 <% if (review_status) { %>
210 210 <i class="icon-circle review-status-<%= review_status %>"></i>
211 211 <% } %>
212 212 <strong>General</strong> TODO (<code>#<%- comment_id -%></code>)
213 213 <% if (version_info) { %>
214 214 <%= version_info %>
215 215 <% } %>
216 216 <% } %>
217 217 <% } else { %>
218 218 <% if (inline) { %>
219 219 <strong>Inline</strong> comment (<code>#<%- comment_id -%></code>) on line: <%= line_no %>
220 220 <% if (version_info) { %>
221 221 <%= version_info %>
222 222 <% } %>
223 223 <br/>
224 224 File: <code><%- file_name -%></code>
225 225 <% } else { %>
226 226 <% if (review_status) { %>
227 227 <i class="icon-circle review-status-<%= review_status %>"></i>
228 228 <% } %>
229 229 <strong>General</strong> comment (<code>#<%- comment_id -%></code>)
230 230 <% if (version_info) { %>
231 231 <%= version_info %>
232 232 <% } %>
233 233 <% } %>
234 234 <% } %>
235 235 <br/>
236 236 Created:
237 237 <time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time>
238 238
239 <% if (is_todo) { %>
240 <div style="text-align: center; padding-top: 5px">
241 <a class="btn btn-sm" href="#resolveTodo<%- comment_id -%>" onclick="Rhodecode.comments.resolveTodo(this, '<%- comment_id -%>'); return false">
242 <strong>Resolve TODO</strong>
243 </a>
244 </div>
245 <% } %>
246
239 247 </div>
240 248
241 249 </script>
242 250
243 251 <script id="ejs_commentHelpHovercard" type="text/template" class="ejsTemplate">
244 252
245 253 <div>
246 254 Use <strong>@username</strong> mention syntax to send direct notification to this RhodeCode user.<br/>
247 255 Typing / starts autocomplete for certain action, e.g set review status, or comment type. <br/>
248 256 <br/>
249 257 Use <strong>Cmd/ctrl+enter</strong> to submit comment, or <strong>Shift+Cmd/ctrl+enter</strong> to submit a draft.<br/>
250 258 <br/>
251 259 <strong>Draft comments</strong> are private to the author, and trigger no notification to others.<br/>
252 260 They are permanent until deleted, or converted to regular comments.<br/>
253 261 <br/>
254 262 <br/>
255 263 </div>
256 264
257 265 </script>
258 266
259 267
260 268
261 269 ##// END OF EJS Templates
262 270 </div>
263 271
264 272
265 273 <script>
266 274 // registers the templates into global cache
267 275 registerTemplates();
268 276 </script>
269 277
270 278 </%text>
@@ -1,206 +1,205 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'user': '@'+h.person(user),
10 10 'repo_name': repo_name,
11 11 'status': status_change,
12 12 'comment_file': comment_file,
13 13 'comment_line': comment_line,
14 14 'comment_type': comment_type,
15 15 'comment_id': comment_id,
16 16
17 'pr_title': pull_request.title,
17 'pr_title': pull_request.title_safe,
18 18 'pr_id': pull_request.pull_request_id,
19 19 'mention_prefix': '[mention] ' if mention else '',
20 20 }
21 21
22 22 if comment_file:
23 23 subject_template = email_pr_comment_file_subject_template or \
24 24 _('{mention_prefix}{user} left a {comment_type} on file `{comment_file}` in pull request !{pr_id}: "{pr_title}"').format(**data)
25 25 else:
26 26 if status_change:
27 27 subject_template = email_pr_comment_status_change_subject_template or \
28 28 _('{mention_prefix}[status: {status}] {user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data)
29 29 else:
30 30 subject_template = email_pr_comment_subject_template or \
31 31 _('{mention_prefix}{user} left a {comment_type} on pull request !{pr_id}: "{pr_title}"').format(**data)
32 32 %>
33 33
34
35 34 ${subject_template.format(**data) |n}
36 35 </%def>
37 36
38 37 ## PLAINTEXT VERSION OF BODY
39 38 <%def name="body_plaintext()" filter="n,trim">
40 39 <%
41 40 data = {
42 41 'user': h.person(user),
43 42 'repo_name': repo_name,
44 43 'status': status_change,
45 44 'comment_file': comment_file,
46 45 'comment_line': comment_line,
47 46 'comment_type': comment_type,
48 47 'comment_id': comment_id,
49 48
50 'pr_title': pull_request.title,
49 'pr_title': pull_request.title_safe,
51 50 'pr_id': pull_request.pull_request_id,
52 51 'source_ref_type': pull_request.source_ref_parts.type,
53 52 'source_ref_name': pull_request.source_ref_parts.name,
54 53 'target_ref_type': pull_request.target_ref_parts.type,
55 54 'target_ref_name': pull_request.target_ref_parts.name,
56 55 'source_repo': pull_request_source_repo.repo_name,
57 56 'target_repo': pull_request_target_repo.repo_name,
58 57 'source_repo_url': pull_request_source_repo_url,
59 58 'target_repo_url': pull_request_target_repo_url,
60 59 }
61 60 %>
62 61
63 62 * ${_('Comment link')}: ${pr_comment_url}
64 63
65 64 * ${_('Pull Request')}: !${pull_request.pull_request_id}
66 65
67 66 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
68 67
69 68 %if status_change and not closing_pr:
70 69 * ${_('{user} submitted pull request !{pr_id} status: *{status}*').format(**data)}
71 70
72 71 %elif status_change and closing_pr:
73 72 * ${_('{user} submitted pull request !{pr_id} status: *{status} and closed*').format(**data)}
74 73
75 74 %endif
76 75 %if comment_file:
77 76 * ${_('File: {comment_file} on line {comment_line}').format(**data)}
78 77
79 78 %endif
80 79 % if comment_type == 'todo':
81 80 ${('Inline' if comment_file else 'General')} ${_('`TODO` number')} ${comment_id}:
82 81 % else:
83 82 ${('Inline' if comment_file else 'General')} ${_('`Note` number')} ${comment_id}:
84 83 % endif
85 84
86 85 ${comment_body |n, trim}
87 86
88 87 ---
89 88 ${self.plaintext_footer()}
90 89 </%def>
91 90
92 91
93 92 <%
94 93 data = {
95 94 'user': h.person(user),
96 95 'comment_file': comment_file,
97 96 'comment_line': comment_line,
98 97 'comment_type': comment_type,
99 98 'comment_id': comment_id,
100 99 'renderer_type': renderer_type or 'plain',
101 100
102 'pr_title': pull_request.title,
101 'pr_title': pull_request.title_safe,
103 102 'pr_id': pull_request.pull_request_id,
104 103 'status': status_change,
105 104 'source_ref_type': pull_request.source_ref_parts.type,
106 105 'source_ref_name': pull_request.source_ref_parts.name,
107 106 'target_ref_type': pull_request.target_ref_parts.type,
108 107 'target_ref_name': pull_request.target_ref_parts.name,
109 108 'source_repo': pull_request_source_repo.repo_name,
110 109 'target_repo': pull_request_target_repo.repo_name,
111 110 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
112 111 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
113 112 }
114 113 %>
115 114
116 115 ## header
117 116 <table style="text-align:left;vertical-align:middle;width: 100%">
118 117 <tr>
119 118 <td style="width:100%;border-bottom:1px solid #dbd9da;">
120 119
121 120 <div style="margin: 0; font-weight: bold">
122 121 <div class="clear-both" style="margin-bottom: 4px">
123 122 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
124 123 ${_('left a')}
125 124 <a href="${pr_comment_url}" style="${base.link_css()}">
126 125 % if comment_file:
127 126 ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)}
128 127 % else:
129 128 ${_('{comment_type} on pull request.').format(**data) |n}
130 129 % endif
131 130 </a>
132 131 </div>
133 132 <div style="margin-top: 10px"></div>
134 133 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
135 134 </div>
136 135
137 136 </td>
138 137 </tr>
139 138
140 139 </table>
141 140 <div class="clear-both"></div>
142 141 ## main body
143 142 <table style="text-align:left;vertical-align:middle;width: 100%">
144 143
145 144 ## spacing def
146 145 <tr>
147 146 <td style="width: 130px"></td>
148 147 <td></td>
149 148 </tr>
150 149
151 150 % if status_change:
152 151 <tr>
153 152 <td style="padding-right:20px;">${_('Review Status')}:</td>
154 153 <td>
155 154 % if closing_pr:
156 155 ${_('Closed pull request with status')}: ${base.status_text(status_change, tag_type=status_change_type)}
157 156 % else:
158 157 ${_('Submitted review status')}: ${base.status_text(status_change, tag_type=status_change_type)}
159 158 % endif
160 159 </td>
161 160 </tr>
162 161 % endif
163 162 <tr>
164 163 <td style="padding-right:20px;">${_('Pull request')}:</td>
165 164 <td>
166 165 <a href="${pull_request_url}" style="${base.link_css()}">
167 166 !${pull_request.pull_request_id}
168 167 </a>
169 168 </td>
170 169 </tr>
171 170
172 171 <tr>
173 172 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
174 173 <td style="line-height:20px;">
175 174 <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']}
176 175 &rarr;
177 176 <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']}
178 177 </td>
179 178 </tr>
180 179
181 180 % if comment_file:
182 181 <tr>
183 182 <td style="padding-right:20px;">${_('File')}:</td>
184 183 <td><a href="${pr_comment_url}" style="${base.link_css()}">${_('`{comment_file}` on line {comment_line}').format(**data)}</a></td>
185 184 </tr>
186 185 % endif
187 186
188 187 <tr style="border-bottom:1px solid #dbd9da;">
189 188 <td colspan="2" style="padding-right:20px;">
190 189 % if comment_type == 'todo':
191 190 ${('Inline' if comment_file else 'General')} ${_('`TODO` number')} ${comment_id}:
192 191 % else:
193 192 ${('Inline' if comment_file else 'General')} ${_('`Note` number')} ${comment_id}:
194 193 % endif
195 194 </td>
196 195 </tr>
197 196
198 197 <tr>
199 198 <td colspan="2" style="background: #F7F7F7">${h.render(comment_body, renderer=data['renderer_type'], mentions=True)}</td>
200 199 </tr>
201 200
202 201 <tr>
203 202 <td><a href="${pr_comment_reply_url}">${_('Reply')}</a></td>
204 203 <td></td>
205 204 </tr>
206 205 </table>
@@ -1,154 +1,154 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'user': '@'+h.person(user),
10 10 'pr_id': pull_request.pull_request_id,
11 'pr_title': pull_request.title,
11 'pr_title': pull_request.title_safe,
12 12 }
13 13
14 14 if user_role == 'observer':
15 15 subject_template = email_pr_review_subject_template or _('{user} added you as observer to pull request. !{pr_id}: "{pr_title}"')
16 16 else:
17 17 subject_template = email_pr_review_subject_template or _('{user} requested a pull request review. !{pr_id}: "{pr_title}"')
18 18 %>
19 19
20 20 ${subject_template.format(**data) |n}
21 21 </%def>
22 22
23 23 ## PLAINTEXT VERSION OF BODY
24 24 <%def name="body_plaintext()" filter="n,trim">
25 25 <%
26 26 data = {
27 27 'user': h.person(user),
28 28 'pr_id': pull_request.pull_request_id,
29 'pr_title': pull_request.title,
29 'pr_title': pull_request.title_safe,
30 30 'source_ref_type': pull_request.source_ref_parts.type,
31 31 'source_ref_name': pull_request.source_ref_parts.name,
32 32 'target_ref_type': pull_request.target_ref_parts.type,
33 33 'target_ref_name': pull_request.target_ref_parts.name,
34 34 'repo_url': pull_request_source_repo_url,
35 35 'source_repo': pull_request_source_repo.repo_name,
36 36 'target_repo': pull_request_target_repo.repo_name,
37 37 'source_repo_url': pull_request_source_repo_url,
38 38 'target_repo_url': pull_request_target_repo_url,
39 39 }
40 40
41 41 %>
42 42
43 43 * ${_('Pull Request link')}: ${pull_request_url}
44 44
45 45 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
46 46
47 47 * ${_('Title')}: ${pull_request.title}
48 48
49 49 * ${_('Description')}:
50 50
51 51 ${pull_request.description | trim}
52 52
53 53
54 54 * ${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
55 55
56 56 % for commit_id, message in pull_request_commits:
57 57 - ${h.short_id(commit_id)}
58 58 ${h.chop_at_smart(message.lstrip(), '\n', suffix_if_chopped='...')}
59 59
60 60 % endfor
61 61
62 62 ---
63 63 ${self.plaintext_footer()}
64 64 </%def>
65 65 <%
66 66 data = {
67 67 'user': h.person(user),
68 68 'pr_id': pull_request.pull_request_id,
69 'pr_title': pull_request.title,
69 'pr_title': pull_request.title_safe,
70 70 'source_ref_type': pull_request.source_ref_parts.type,
71 71 'source_ref_name': pull_request.source_ref_parts.name,
72 72 'target_ref_type': pull_request.target_ref_parts.type,
73 73 'target_ref_name': pull_request.target_ref_parts.name,
74 74 'repo_url': pull_request_source_repo_url,
75 75 'source_repo': pull_request_source_repo.repo_name,
76 76 'target_repo': pull_request_target_repo.repo_name,
77 77 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
78 78 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
79 79 }
80 80 %>
81 81 ## header
82 82 <table style="text-align:left;vertical-align:middle;width: 100%">
83 83 <tr>
84 84 <td style="width:100%;border-bottom:1px solid #dbd9da;">
85 85 <div style="margin: 0; font-weight: bold">
86 86 % if user_role == 'observer':
87 87 <div class="clear-both" class="clear-both" style="margin-bottom: 4px">
88 88 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
89 89 ${_('added you as observer to')}
90 90 <a href="${pull_request_url}" style="${base.link_css()}">pull request</a>.
91 91 </div>
92 92 % else:
93 93 <div class="clear-both" class="clear-both" style="margin-bottom: 4px">
94 94 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
95 95 ${_('requested a')}
96 96 <a href="${pull_request_url}" style="${base.link_css()}">pull request</a> review.
97 97 </div>
98 98 % endif
99 99 <div style="margin-top: 10px"></div>
100 100 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
101 101 </div>
102 102 </td>
103 103 </tr>
104 104
105 105 </table>
106 106 <div class="clear-both"></div>
107 107 ## main body
108 108 <table style="text-align:left;vertical-align:middle;width: 100%">
109 109 ## spacing def
110 110 <tr>
111 111 <td style="width: 130px"></td>
112 112 <td></td>
113 113 </tr>
114 114
115 115 <tr>
116 116 <td style="padding-right:20px;">${_('Pull request')}:</td>
117 117 <td>
118 118 <a href="${pull_request_url}" style="${base.link_css()}">
119 119 !${pull_request.pull_request_id}
120 120 </a>
121 121 </td>
122 122 </tr>
123 123
124 124 <tr>
125 125 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
126 126 <td style="line-height:20px;">
127 127 <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']}
128 128 &rarr;
129 129 <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']}
130 130 </td>
131 131 </tr>
132 132
133 133 <tr>
134 134 <td style="padding-right:20px;">${_('Description')}:</td>
135 135 <td style="white-space:pre-wrap"><code>${pull_request.description | trim}</code></td>
136 136 </tr>
137 137 <tr>
138 138 <td style="padding-right:20px;">${_ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits)) % {'num': len(pull_request_commits)}}:</td>
139 139 <td></td>
140 140 </tr>
141 141
142 142 <tr>
143 143 <td colspan="2">
144 144 <ol style="margin:0 0 0 1em;padding:0;text-align:left;">
145 145 % for commit_id, message in pull_request_commits:
146 146 <li style="margin:0 0 1em;">
147 147 <pre style="margin:0 0 .5em"><a href="${h.route_path('repo_commit', repo_name=pull_request_source_repo.repo_name, commit_id=commit_id)}" style="${base.link_css()}">${h.short_id(commit_id)}</a></pre>
148 148 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
149 149 </li>
150 150 % endfor
151 151 </ol>
152 152 </td>
153 153 </tr>
154 154 </table>
@@ -1,172 +1,172 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3 <%namespace name="base" file="base.mako"/>
4 4
5 5 ## EMAIL SUBJECT
6 6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 7 <%
8 8 data = {
9 9 'updating_user': '@'+h.person(updating_user),
10 10 'pr_id': pull_request.pull_request_id,
11 'pr_title': pull_request.title,
11 'pr_title': pull_request.title_safe,
12 12 }
13 13
14 14 subject_template = email_pr_update_subject_template or _('{updating_user} updated pull request. !{pr_id}: "{pr_title}"')
15 15 %>
16 16
17 17 ${subject_template.format(**data) |n}
18 18 </%def>
19 19
20 20 ## PLAINTEXT VERSION OF BODY
21 21 <%def name="body_plaintext()" filter="n,trim">
22 22 <%
23 23 data = {
24 24 'updating_user': h.person(updating_user),
25 25 'pr_id': pull_request.pull_request_id,
26 'pr_title': pull_request.title,
26 'pr_title': pull_request.title_safe,
27 27 'source_ref_type': pull_request.source_ref_parts.type,
28 28 'source_ref_name': pull_request.source_ref_parts.name,
29 29 'target_ref_type': pull_request.target_ref_parts.type,
30 30 'target_ref_name': pull_request.target_ref_parts.name,
31 31 'repo_url': pull_request_source_repo_url,
32 32 'source_repo': pull_request_source_repo.repo_name,
33 33 'target_repo': pull_request_target_repo.repo_name,
34 34 'source_repo_url': pull_request_source_repo_url,
35 35 'target_repo_url': pull_request_target_repo_url,
36 36 }
37 37 %>
38 38
39 39 * ${_('Pull Request link')}: ${pull_request_url}
40 40
41 41 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
42 42
43 43 * ${_('Title')}: ${pull_request.title}
44 44
45 45 * ${_('Description')}:
46 46
47 47 ${pull_request.description | trim}
48 48
49 49 * Changed commits:
50 50
51 51 - Added: ${len(added_commits)}
52 52 - Removed: ${len(removed_commits)}
53 53
54 54 * Changed files:
55 55
56 56 %if not changed_files:
57 57 No file changes found
58 58 %else:
59 59 %for file_name in added_files:
60 60 - A `${file_name}`
61 61 %endfor
62 62 %for file_name in modified_files:
63 63 - M `${file_name}`
64 64 %endfor
65 65 %for file_name in removed_files:
66 66 - R `${file_name}`
67 67 %endfor
68 68 %endif
69 69
70 70 ---
71 71 ${self.plaintext_footer()}
72 72 </%def>
73 73 <%
74 74 data = {
75 75 'updating_user': h.person(updating_user),
76 76 'pr_id': pull_request.pull_request_id,
77 'pr_title': pull_request.title,
77 'pr_title': pull_request.title_safe,
78 78 'source_ref_type': pull_request.source_ref_parts.type,
79 79 'source_ref_name': pull_request.source_ref_parts.name,
80 80 'target_ref_type': pull_request.target_ref_parts.type,
81 81 'target_ref_name': pull_request.target_ref_parts.name,
82 82 'repo_url': pull_request_source_repo_url,
83 83 'source_repo': pull_request_source_repo.repo_name,
84 84 'target_repo': pull_request_target_repo.repo_name,
85 85 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
86 86 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
87 87 }
88 88 %>
89 89
90 90 ## header
91 91 <table style="text-align:left;vertical-align:middle;width: 100%">
92 92 <tr>
93 93 <td style="width:100%;border-bottom:1px solid #dbd9da;">
94 94
95 95 <div style="margin: 0; font-weight: bold">
96 96 <div class="clear-both" style="margin-bottom: 4px">
97 97 <span style="color:#7E7F7F">@${h.person(updating_user.username)}</span>
98 98 ${_('updated')}
99 99 <a href="${pull_request_url}" style="${base.link_css()}">
100 100 ${_('pull request.').format(**data) }
101 101 </a>
102 102 </div>
103 103 <div style="margin-top: 10px"></div>
104 104 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
105 105 </div>
106 106
107 107 </td>
108 108 </tr>
109 109
110 110 </table>
111 111 <div class="clear-both"></div>
112 112 ## main body
113 113 <table style="text-align:left;vertical-align:middle;width: 100%">
114 114 ## spacing def
115 115 <tr>
116 116 <td style="width: 130px"></td>
117 117 <td></td>
118 118 </tr>
119 119
120 120 <tr>
121 121 <td style="padding-right:20px;">${_('Pull request')}:</td>
122 122 <td>
123 123 <a href="${pull_request_url}" style="${base.link_css()}">
124 124 !${pull_request.pull_request_id}
125 125 </a>
126 126 </td>
127 127 </tr>
128 128
129 129 <tr>
130 130 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
131 131 <td style="line-height:20px;">
132 132 <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']}
133 133 &rarr;
134 134 <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']}
135 135 </td>
136 136 </tr>
137 137
138 138 <tr>
139 139 <td style="padding-right:20px;">${_('Description')}:</td>
140 140 <td style="white-space:pre-wrap"><code>${pull_request.description | trim}</code></td>
141 141 </tr>
142 142 <tr>
143 143 <td style="padding-right:20px;">${_('Changes')}:</td>
144 144 <td>
145 145 <strong>Changed commits:</strong>
146 146 <ul class="changes-ul">
147 147 <li>- Added: ${len(added_commits)}</li>
148 148 <li>- Removed: ${len(removed_commits)}</li>
149 149 </ul>
150 150
151 151 <strong>Changed files:</strong>
152 152 <ul class="changes-ul">
153 153
154 154 %if not changed_files:
155 155 <li>No file changes found</li>
156 156 %else:
157 157 %for file_name in added_files:
158 158 <li>- A <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a></li>
159 159 %endfor
160 160 %for file_name in modified_files:
161 161 <li>- M <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a></li>
162 162 %endfor
163 163 %for file_name in removed_files:
164 164 <li>- R <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a></li>
165 165 %endfor
166 166 %endif
167 167
168 168 </ul>
169 169 </td>
170 170 </tr>
171 171
172 172 </table>
@@ -1,195 +1,197 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22 import collections
23 23
24 24 from rhodecode.lib.partial_renderer import PyramidPartialRenderer
25 25 from rhodecode.lib.utils2 import AttributeDict
26 26 from rhodecode.model.db import User, PullRequestReviewers
27 27 from rhodecode.model.notification import EmailNotificationModel
28 28
29 29
30 @pytest.fixture()
31 def pr():
32 def factory(ref):
33 return collections.namedtuple(
34 'PullRequest',
35 'pull_request_id, title, title_safe, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')\
36 (200, 'Example Pull Request', 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
37 return factory
38
39
30 40 def test_get_template_obj(app, request_stub):
31 41 template = EmailNotificationModel().get_renderer(
32 42 EmailNotificationModel.TYPE_TEST, request_stub)
33 43 assert isinstance(template, PyramidPartialRenderer)
34 44
35 45
36 46 def test_render_email(app, http_host_only_stub):
37 47 kwargs = {}
38 48 subject, body, body_plaintext = EmailNotificationModel().render_email(
39 49 EmailNotificationModel.TYPE_TEST, **kwargs)
40 50
41 51 # subject
42 52 assert subject == 'Test "Subject" hello "world"'
43 53
44 54 # body plaintext
45 55 assert body_plaintext == 'Email Plaintext Body'
46 56
47 57 # body
48 58 notification_footer1 = 'This is a notification from RhodeCode.'
49 59 notification_footer2 = 'http://{}/'.format(http_host_only_stub)
50 60 assert notification_footer1 in body
51 61 assert notification_footer2 in body
52 62 assert 'Email Body' in body
53 63
54 64
55 65 @pytest.mark.parametrize('role', PullRequestReviewers.ROLES)
56 def test_render_pr_email(app, user_admin, role):
66 def test_render_pr_email(app, user_admin, role, pr):
57 67 ref = collections.namedtuple(
58 68 'Ref', 'name, type')('fxies123', 'book')
59
60 pr = collections.namedtuple('PullRequest',
61 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
62 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
63
69 pr = pr(ref)
64 70 source_repo = target_repo = collections.namedtuple(
65 71 'Repo', 'type, repo_name')('hg', 'pull_request_1')
66 72
67 73 kwargs = {
68 74 'user': User.get_first_super_admin(),
69 75 'pull_request': pr,
70 76 'pull_request_commits': [],
71 77
72 78 'pull_request_target_repo': target_repo,
73 79 'pull_request_target_repo_url': 'x',
74 80
75 81 'pull_request_source_repo': source_repo,
76 82 'pull_request_source_repo_url': 'x',
77 83
78 84 'pull_request_url': 'http://localhost/pr1',
79 85 'user_role': role,
80 86 }
81 87
82 88 subject, body, body_plaintext = EmailNotificationModel().render_email(
83 89 EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
84 90
85 91 # subject
86 92 if role == PullRequestReviewers.ROLE_REVIEWER:
87 93 assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
88 94 elif role == PullRequestReviewers.ROLE_OBSERVER:
89 95 assert subject == '@test_admin (RhodeCode Admin) added you as observer to pull request. !200: "Example Pull Request"'
90 96
91 97
92 def test_render_pr_update_email(app, user_admin):
98 def test_render_pr_update_email(app, user_admin, pr):
93 99 ref = collections.namedtuple(
94 100 'Ref', 'name, type')('fxies123', 'book')
95 101
96 pr = collections.namedtuple('PullRequest',
97 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
98 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
102 pr = pr(ref)
99 103
100 104 source_repo = target_repo = collections.namedtuple(
101 105 'Repo', 'type, repo_name')('hg', 'pull_request_1')
102 106
103 107 commit_changes = AttributeDict({
104 108 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
105 109 'removed': ['eeeeeeeeeee'],
106 110 })
107 111 file_changes = AttributeDict({
108 112 'added': ['a/file1.md', 'file2.py'],
109 113 'modified': ['b/modified_file.rst'],
110 114 'removed': ['.idea'],
111 115 })
112 116
113 117 kwargs = {
114 118 'updating_user': User.get_first_super_admin(),
115 119
116 120 'pull_request': pr,
117 121 'pull_request_commits': [],
118 122
119 123 'pull_request_target_repo': target_repo,
120 124 'pull_request_target_repo_url': 'x',
121 125
122 126 'pull_request_source_repo': source_repo,
123 127 'pull_request_source_repo_url': 'x',
124 128
125 129 'pull_request_url': 'http://localhost/pr1',
126 130
127 131 'pr_comment_url': 'http://comment-url',
128 132 'pr_comment_reply_url': 'http://comment-url#reply',
129 133 'ancestor_commit_id': 'f39bd443',
130 134 'added_commits': commit_changes.added,
131 135 'removed_commits': commit_changes.removed,
132 136 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
133 137 'added_files': file_changes.added,
134 138 'modified_files': file_changes.modified,
135 139 'removed_files': file_changes.removed,
136 140 }
137 141
138 142 subject, body, body_plaintext = EmailNotificationModel().render_email(
139 143 EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, **kwargs)
140 144
141 145 # subject
142 146 assert subject == '@test_admin (RhodeCode Admin) updated pull request. !200: "Example Pull Request"'
143 147
144 148
145 149 @pytest.mark.parametrize('mention', [
146 150 True,
147 151 False
148 152 ])
149 153 @pytest.mark.parametrize('email_type', [
150 154 EmailNotificationModel.TYPE_COMMIT_COMMENT,
151 155 EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
152 156 ])
153 def test_render_comment_subject_no_newlines(app, mention, email_type):
157 def test_render_comment_subject_no_newlines(app, mention, email_type, pr):
154 158 ref = collections.namedtuple(
155 159 'Ref', 'name, type')('fxies123', 'book')
156 160
157 pr = collections.namedtuple('PullRequest',
158 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
159 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
161 pr = pr(ref)
160 162
161 163 source_repo = target_repo = collections.namedtuple(
162 164 'Repo', 'type, repo_name')('hg', 'pull_request_1')
163 165
164 166 kwargs = {
165 167 'user': User.get_first_super_admin(),
166 168 'commit': AttributeDict(raw_id='a'*40, message='Commit message'),
167 169 'status_change': 'approved',
168 170 'commit_target_repo_url': 'http://foo.example.com/#comment1',
169 171 'repo_name': 'test-repo',
170 172 'comment_file': 'test-file.py',
171 173 'comment_line': 'n100',
172 174 'comment_type': 'note',
173 175 'comment_id': 2048,
174 176 'commit_comment_url': 'http://comment-url',
175 177 'commit_comment_reply_url': 'http://comment-url/#Reply',
176 178 'instance_url': 'http://rc-instance',
177 179 'comment_body': 'hello world',
178 180 'mention': mention,
179 181
180 182 'pr_comment_url': 'http://comment-url',
181 183 'pr_comment_reply_url': 'http://comment-url/#Reply',
182 184 'pull_request': pr,
183 185 'pull_request_commits': [],
184 186
185 187 'pull_request_target_repo': target_repo,
186 188 'pull_request_target_repo_url': 'x',
187 189
188 190 'pull_request_source_repo': source_repo,
189 191 'pull_request_source_repo_url': 'x',
190 192
191 193 'pull_request_url': 'http://code.rc.com/_pr/123'
192 194 }
193 195 subject, body, body_plaintext = EmailNotificationModel().render_email(email_type, **kwargs)
194 196
195 197 assert '\n' not in subject
General Comments 0
You need to be logged in to leave comments. Login now