Show More
@@ -0,0 +1,28 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | """ | |||
|
3 | rhodecode.model.db_1_4_0 | |||
|
4 | ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
|
5 | ||||
|
6 | Database Models for RhodeCode <=1.5.X | |||
|
7 | ||||
|
8 | :created_on: Apr 08, 2010 | |||
|
9 | :author: marcink | |||
|
10 | :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> | |||
|
11 | :license: GPLv3, see COPYING for more details. | |||
|
12 | """ | |||
|
13 | # This program is free software: you can redistribute it and/or modify | |||
|
14 | # it under the terms of the GNU General Public License as published by | |||
|
15 | # the Free Software Foundation, either version 3 of the License, or | |||
|
16 | # (at your option) any later version. | |||
|
17 | # | |||
|
18 | # This program is distributed in the hope that it will be useful, | |||
|
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
21 | # GNU General Public License for more details. | |||
|
22 | # | |||
|
23 | # You should have received a copy of the GNU General Public License | |||
|
24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
25 | ||||
|
26 | #TODO: replace that will db.py content after 1.6 Release | |||
|
27 | ||||
|
28 | from rhodecode.model.db import * |
@@ -0,0 +1,50 b'' | |||||
|
1 | import logging | |||
|
2 | import datetime | |||
|
3 | ||||
|
4 | from sqlalchemy import * | |||
|
5 | from sqlalchemy.exc import DatabaseError | |||
|
6 | from sqlalchemy.orm import relation, backref, class_mapper, joinedload | |||
|
7 | from sqlalchemy.orm.session import Session | |||
|
8 | from sqlalchemy.ext.declarative import declarative_base | |||
|
9 | ||||
|
10 | from rhodecode.lib.dbmigrate.migrate import * | |||
|
11 | from rhodecode.lib.dbmigrate.migrate.changeset import * | |||
|
12 | ||||
|
13 | from rhodecode.model.meta import Base | |||
|
14 | from rhodecode.model import meta | |||
|
15 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
|
16 | ||||
|
17 | log = logging.getLogger(__name__) | |||
|
18 | ||||
|
19 | ||||
|
20 | def upgrade(migrate_engine): | |||
|
21 | """ | |||
|
22 | Upgrade operations go here. | |||
|
23 | Don't create your own engine; bind migrate_engine to your metadata | |||
|
24 | """ | |||
|
25 | _reset_base(migrate_engine) | |||
|
26 | #========================================================================== | |||
|
27 | # USER LOGS | |||
|
28 | #========================================================================== | |||
|
29 | from rhodecode.lib.dbmigrate.schema.db_1_5_2 import UserIpMap | |||
|
30 | tbl = UserIpMap.__table__ | |||
|
31 | tbl.create() | |||
|
32 | ||||
|
33 | #========================================================================== | |||
|
34 | # REPOSITORIES | |||
|
35 | #========================================================================== | |||
|
36 | from rhodecode.lib.dbmigrate.schema.db_1_5_2 import Repository | |||
|
37 | tbl = Repository.__table__ | |||
|
38 | changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) | |||
|
39 | # create username column | |||
|
40 | changeset_cache.create(table=tbl) | |||
|
41 | ||||
|
42 | #fix cache data | |||
|
43 | repositories = Repository.getAll() | |||
|
44 | for entry in repositories: | |||
|
45 | entry.update_changeset_cache() | |||
|
46 | ||||
|
47 | ||||
|
48 | def downgrade(migrate_engine): | |||
|
49 | meta = MetaData() | |||
|
50 | meta.bind = migrate_engine |
This diff has been collapsed as it changes many lines, (1901 lines changed) Show them Hide them | |||||
@@ -0,0 +1,1901 b'' | |||||
|
1 | # Copyright 2007 Google Inc. | |||
|
2 | # Licensed to PSF under a Contributor Agreement. | |||
|
3 | # | |||
|
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | |||
|
5 | # you may not use this file except in compliance with the License. | |||
|
6 | # You may obtain a copy of the License at | |||
|
7 | # | |||
|
8 | # http://www.apache.org/licenses/LICENSE-2.0 | |||
|
9 | # | |||
|
10 | # Unless required by applicable law or agreed to in writing, software | |||
|
11 | # distributed under the License is distributed on an "AS IS" BASIS, | |||
|
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
|
13 | # implied. See the License for the specific language governing | |||
|
14 | # permissions and limitations under the License. | |||
|
15 | ||||
|
16 | """A fast, lightweight IPv4/IPv6 manipulation library in Python. | |||
|
17 | ||||
|
18 | This library is used to create/poke/manipulate IPv4 and IPv6 addresses | |||
|
19 | and networks. | |||
|
20 | ||||
|
21 | """ | |||
|
22 | ||||
|
23 | __version__ = 'trunk' | |||
|
24 | ||||
|
25 | import struct | |||
|
26 | ||||
|
27 | IPV4LENGTH = 32 | |||
|
28 | IPV6LENGTH = 128 | |||
|
29 | ||||
|
30 | ||||
|
31 | class AddressValueError(ValueError): | |||
|
32 | """A Value Error related to the address.""" | |||
|
33 | ||||
|
34 | ||||
|
35 | class NetmaskValueError(ValueError): | |||
|
36 | """A Value Error related to the netmask.""" | |||
|
37 | ||||
|
38 | ||||
|
39 | def IPAddress(address, version=None): | |||
|
40 | """Take an IP string/int and return an object of the correct type. | |||
|
41 | ||||
|
42 | Args: | |||
|
43 | address: A string or integer, the IP address. Either IPv4 or | |||
|
44 | IPv6 addresses may be supplied; integers less than 2**32 will | |||
|
45 | be considered to be IPv4 by default. | |||
|
46 | version: An Integer, 4 or 6. If set, don't try to automatically | |||
|
47 | determine what the IP address type is. important for things | |||
|
48 | like IPAddress(1), which could be IPv4, '0.0.0.1', or IPv6, | |||
|
49 | '::1'. | |||
|
50 | ||||
|
51 | Returns: | |||
|
52 | An IPv4Address or IPv6Address object. | |||
|
53 | ||||
|
54 | Raises: | |||
|
55 | ValueError: if the string passed isn't either a v4 or a v6 | |||
|
56 | address. | |||
|
57 | ||||
|
58 | """ | |||
|
59 | if version: | |||
|
60 | if version == 4: | |||
|
61 | return IPv4Address(address) | |||
|
62 | elif version == 6: | |||
|
63 | return IPv6Address(address) | |||
|
64 | ||||
|
65 | try: | |||
|
66 | return IPv4Address(address) | |||
|
67 | except (AddressValueError, NetmaskValueError): | |||
|
68 | pass | |||
|
69 | ||||
|
70 | try: | |||
|
71 | return IPv6Address(address) | |||
|
72 | except (AddressValueError, NetmaskValueError): | |||
|
73 | pass | |||
|
74 | ||||
|
75 | raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % | |||
|
76 | address) | |||
|
77 | ||||
|
78 | ||||
|
79 | def IPNetwork(address, version=None, strict=False): | |||
|
80 | """Take an IP string/int and return an object of the correct type. | |||
|
81 | ||||
|
82 | Args: | |||
|
83 | address: A string or integer, the IP address. Either IPv4 or | |||
|
84 | IPv6 addresses may be supplied; integers less than 2**32 will | |||
|
85 | be considered to be IPv4 by default. | |||
|
86 | version: An Integer, if set, don't try to automatically | |||
|
87 | determine what the IP address type is. important for things | |||
|
88 | like IPNetwork(1), which could be IPv4, '0.0.0.1/32', or IPv6, | |||
|
89 | '::1/128'. | |||
|
90 | ||||
|
91 | Returns: | |||
|
92 | An IPv4Network or IPv6Network object. | |||
|
93 | ||||
|
94 | Raises: | |||
|
95 | ValueError: if the string passed isn't either a v4 or a v6 | |||
|
96 | address. Or if a strict network was requested and a strict | |||
|
97 | network wasn't given. | |||
|
98 | ||||
|
99 | """ | |||
|
100 | if version: | |||
|
101 | if version == 4: | |||
|
102 | return IPv4Network(address, strict) | |||
|
103 | elif version == 6: | |||
|
104 | return IPv6Network(address, strict) | |||
|
105 | ||||
|
106 | try: | |||
|
107 | return IPv4Network(address, strict) | |||
|
108 | except (AddressValueError, NetmaskValueError): | |||
|
109 | pass | |||
|
110 | ||||
|
111 | try: | |||
|
112 | return IPv6Network(address, strict) | |||
|
113 | except (AddressValueError, NetmaskValueError): | |||
|
114 | pass | |||
|
115 | ||||
|
116 | raise ValueError('%r does not appear to be an IPv4 or IPv6 network' % | |||
|
117 | address) | |||
|
118 | ||||
|
119 | ||||
|
120 | def v4_int_to_packed(address): | |||
|
121 | """The binary representation of this address. | |||
|
122 | ||||
|
123 | Args: | |||
|
124 | address: An integer representation of an IPv4 IP address. | |||
|
125 | ||||
|
126 | Returns: | |||
|
127 | The binary representation of this address. | |||
|
128 | ||||
|
129 | Raises: | |||
|
130 | ValueError: If the integer is too large to be an IPv4 IP | |||
|
131 | address. | |||
|
132 | """ | |||
|
133 | if address > _BaseV4._ALL_ONES: | |||
|
134 | raise ValueError('Address too large for IPv4') | |||
|
135 | return Bytes(struct.pack('!I', address)) | |||
|
136 | ||||
|
137 | ||||
|
138 | def v6_int_to_packed(address): | |||
|
139 | """The binary representation of this address. | |||
|
140 | ||||
|
141 | Args: | |||
|
142 | address: An integer representation of an IPv6 IP address. | |||
|
143 | ||||
|
144 | Returns: | |||
|
145 | The binary representation of this address. | |||
|
146 | """ | |||
|
147 | return Bytes(struct.pack('!QQ', address >> 64, address & (2 ** 64 - 1))) | |||
|
148 | ||||
|
149 | ||||
|
150 | def _find_address_range(addresses): | |||
|
151 | """Find a sequence of addresses. | |||
|
152 | ||||
|
153 | Args: | |||
|
154 | addresses: a list of IPv4 or IPv6 addresses. | |||
|
155 | ||||
|
156 | Returns: | |||
|
157 | A tuple containing the first and last IP addresses in the sequence. | |||
|
158 | ||||
|
159 | """ | |||
|
160 | first = last = addresses[0] | |||
|
161 | for ip in addresses[1:]: | |||
|
162 | if ip._ip == last._ip + 1: | |||
|
163 | last = ip | |||
|
164 | else: | |||
|
165 | break | |||
|
166 | return (first, last) | |||
|
167 | ||||
|
168 | ||||
|
169 | def _get_prefix_length(number1, number2, bits): | |||
|
170 | """Get the number of leading bits that are same for two numbers. | |||
|
171 | ||||
|
172 | Args: | |||
|
173 | number1: an integer. | |||
|
174 | number2: another integer. | |||
|
175 | bits: the maximum number of bits to compare. | |||
|
176 | ||||
|
177 | Returns: | |||
|
178 | The number of leading bits that are the same for two numbers. | |||
|
179 | ||||
|
180 | """ | |||
|
181 | for i in range(bits): | |||
|
182 | if number1 >> i == number2 >> i: | |||
|
183 | return bits - i | |||
|
184 | return 0 | |||
|
185 | ||||
|
186 | ||||
|
187 | def _count_righthand_zero_bits(number, bits): | |||
|
188 | """Count the number of zero bits on the right hand side. | |||
|
189 | ||||
|
190 | Args: | |||
|
191 | number: an integer. | |||
|
192 | bits: maximum number of bits to count. | |||
|
193 | ||||
|
194 | Returns: | |||
|
195 | The number of zero bits on the right hand side of the number. | |||
|
196 | ||||
|
197 | """ | |||
|
198 | if number == 0: | |||
|
199 | return bits | |||
|
200 | for i in range(bits): | |||
|
201 | if (number >> i) % 2: | |||
|
202 | return i | |||
|
203 | ||||
|
204 | ||||
|
205 | def summarize_address_range(first, last): | |||
|
206 | """Summarize a network range given the first and last IP addresses. | |||
|
207 | ||||
|
208 | Example: | |||
|
209 | >>> summarize_address_range(IPv4Address('1.1.1.0'), | |||
|
210 | IPv4Address('1.1.1.130')) | |||
|
211 | [IPv4Network('1.1.1.0/25'), IPv4Network('1.1.1.128/31'), | |||
|
212 | IPv4Network('1.1.1.130/32')] | |||
|
213 | ||||
|
214 | Args: | |||
|
215 | first: the first IPv4Address or IPv6Address in the range. | |||
|
216 | last: the last IPv4Address or IPv6Address in the range. | |||
|
217 | ||||
|
218 | Returns: | |||
|
219 | The address range collapsed to a list of IPv4Network's or | |||
|
220 | IPv6Network's. | |||
|
221 | ||||
|
222 | Raise: | |||
|
223 | TypeError: | |||
|
224 | If the first and last objects are not IP addresses. | |||
|
225 | If the first and last objects are not the same version. | |||
|
226 | ValueError: | |||
|
227 | If the last object is not greater than the first. | |||
|
228 | If the version is not 4 or 6. | |||
|
229 | ||||
|
230 | """ | |||
|
231 | if not (isinstance(first, _BaseIP) and isinstance(last, _BaseIP)): | |||
|
232 | raise TypeError('first and last must be IP addresses, not networks') | |||
|
233 | if first.version != last.version: | |||
|
234 | raise TypeError("%s and %s are not of the same version" % ( | |||
|
235 | str(first), str(last))) | |||
|
236 | if first > last: | |||
|
237 | raise ValueError('last IP address must be greater than first') | |||
|
238 | ||||
|
239 | networks = [] | |||
|
240 | ||||
|
241 | if first.version == 4: | |||
|
242 | ip = IPv4Network | |||
|
243 | elif first.version == 6: | |||
|
244 | ip = IPv6Network | |||
|
245 | else: | |||
|
246 | raise ValueError('unknown IP version') | |||
|
247 | ||||
|
248 | ip_bits = first._max_prefixlen | |||
|
249 | first_int = first._ip | |||
|
250 | last_int = last._ip | |||
|
251 | while first_int <= last_int: | |||
|
252 | nbits = _count_righthand_zero_bits(first_int, ip_bits) | |||
|
253 | current = None | |||
|
254 | while nbits >= 0: | |||
|
255 | addend = 2 ** nbits - 1 | |||
|
256 | current = first_int + addend | |||
|
257 | nbits -= 1 | |||
|
258 | if current <= last_int: | |||
|
259 | break | |||
|
260 | prefix = _get_prefix_length(first_int, current, ip_bits) | |||
|
261 | net = ip('%s/%d' % (str(first), prefix)) | |||
|
262 | networks.append(net) | |||
|
263 | if current == ip._ALL_ONES: | |||
|
264 | break | |||
|
265 | first_int = current + 1 | |||
|
266 | first = IPAddress(first_int, version=first._version) | |||
|
267 | return networks | |||
|
268 | ||||
|
269 | ||||
|
270 | def _collapse_address_list_recursive(addresses): | |||
|
271 | """Loops through the addresses, collapsing concurrent netblocks. | |||
|
272 | ||||
|
273 | Example: | |||
|
274 | ||||
|
275 | ip1 = IPv4Network('1.1.0.0/24') | |||
|
276 | ip2 = IPv4Network('1.1.1.0/24') | |||
|
277 | ip3 = IPv4Network('1.1.2.0/24') | |||
|
278 | ip4 = IPv4Network('1.1.3.0/24') | |||
|
279 | ip5 = IPv4Network('1.1.4.0/24') | |||
|
280 | ip6 = IPv4Network('1.1.0.1/22') | |||
|
281 | ||||
|
282 | _collapse_address_list_recursive([ip1, ip2, ip3, ip4, ip5, ip6]) -> | |||
|
283 | [IPv4Network('1.1.0.0/22'), IPv4Network('1.1.4.0/24')] | |||
|
284 | ||||
|
285 | This shouldn't be called directly; it is called via | |||
|
286 | collapse_address_list([]). | |||
|
287 | ||||
|
288 | Args: | |||
|
289 | addresses: A list of IPv4Network's or IPv6Network's | |||
|
290 | ||||
|
291 | Returns: | |||
|
292 | A list of IPv4Network's or IPv6Network's depending on what we were | |||
|
293 | passed. | |||
|
294 | ||||
|
295 | """ | |||
|
296 | ret_array = [] | |||
|
297 | optimized = False | |||
|
298 | ||||
|
299 | for cur_addr in addresses: | |||
|
300 | if not ret_array: | |||
|
301 | ret_array.append(cur_addr) | |||
|
302 | continue | |||
|
303 | if cur_addr in ret_array[-1]: | |||
|
304 | optimized = True | |||
|
305 | elif cur_addr == ret_array[-1].supernet().subnet()[1]: | |||
|
306 | ret_array.append(ret_array.pop().supernet()) | |||
|
307 | optimized = True | |||
|
308 | else: | |||
|
309 | ret_array.append(cur_addr) | |||
|
310 | ||||
|
311 | if optimized: | |||
|
312 | return _collapse_address_list_recursive(ret_array) | |||
|
313 | ||||
|
314 | return ret_array | |||
|
315 | ||||
|
316 | ||||
|
317 | def collapse_address_list(addresses): | |||
|
318 | """Collapse a list of IP objects. | |||
|
319 | ||||
|
320 | Example: | |||
|
321 | collapse_address_list([IPv4('1.1.0.0/24'), IPv4('1.1.1.0/24')]) -> | |||
|
322 | [IPv4('1.1.0.0/23')] | |||
|
323 | ||||
|
324 | Args: | |||
|
325 | addresses: A list of IPv4Network or IPv6Network objects. | |||
|
326 | ||||
|
327 | Returns: | |||
|
328 | A list of IPv4Network or IPv6Network objects depending on what we | |||
|
329 | were passed. | |||
|
330 | ||||
|
331 | Raises: | |||
|
332 | TypeError: If passed a list of mixed version objects. | |||
|
333 | ||||
|
334 | """ | |||
|
335 | i = 0 | |||
|
336 | addrs = [] | |||
|
337 | ips = [] | |||
|
338 | nets = [] | |||
|
339 | ||||
|
340 | # split IP addresses and networks | |||
|
341 | for ip in addresses: | |||
|
342 | if isinstance(ip, _BaseIP): | |||
|
343 | if ips and ips[-1]._version != ip._version: | |||
|
344 | raise TypeError("%s and %s are not of the same version" % ( | |||
|
345 | str(ip), str(ips[-1]))) | |||
|
346 | ips.append(ip) | |||
|
347 | elif ip._prefixlen == ip._max_prefixlen: | |||
|
348 | if ips and ips[-1]._version != ip._version: | |||
|
349 | raise TypeError("%s and %s are not of the same version" % ( | |||
|
350 | str(ip), str(ips[-1]))) | |||
|
351 | ips.append(ip.ip) | |||
|
352 | else: | |||
|
353 | if nets and nets[-1]._version != ip._version: | |||
|
354 | raise TypeError("%s and %s are not of the same version" % ( | |||
|
355 | str(ip), str(nets[-1]))) | |||
|
356 | nets.append(ip) | |||
|
357 | ||||
|
358 | # sort and dedup | |||
|
359 | ips = sorted(set(ips)) | |||
|
360 | nets = sorted(set(nets)) | |||
|
361 | ||||
|
362 | while i < len(ips): | |||
|
363 | (first, last) = _find_address_range(ips[i:]) | |||
|
364 | i = ips.index(last) + 1 | |||
|
365 | addrs.extend(summarize_address_range(first, last)) | |||
|
366 | ||||
|
367 | return _collapse_address_list_recursive(sorted( | |||
|
368 | addrs + nets, key=_BaseNet._get_networks_key)) | |||
|
369 | ||||
|
370 | # backwards compatibility | |||
|
371 | CollapseAddrList = collapse_address_list | |||
|
372 | ||||
|
373 | # We need to distinguish between the string and packed-bytes representations | |||
|
374 | # of an IP address. For example, b'0::1' is the IPv4 address 48.58.58.49, | |||
|
375 | # while '0::1' is an IPv6 address. | |||
|
376 | # | |||
|
377 | # In Python 3, the native 'bytes' type already provides this functionality, | |||
|
378 | # so we use it directly. For earlier implementations where bytes is not a | |||
|
379 | # distinct type, we create a subclass of str to serve as a tag. | |||
|
380 | # | |||
|
381 | # Usage example (Python 2): | |||
|
382 | # ip = ipaddr.IPAddress(ipaddr.Bytes('xxxx')) | |||
|
383 | # | |||
|
384 | # Usage example (Python 3): | |||
|
385 | # ip = ipaddr.IPAddress(b'xxxx') | |||
|
386 | try: | |||
|
387 | if bytes is str: | |||
|
388 | raise TypeError("bytes is not a distinct type") | |||
|
389 | Bytes = bytes | |||
|
390 | except (NameError, TypeError): | |||
|
391 | class Bytes(str): | |||
|
392 | def __repr__(self): | |||
|
393 | return 'Bytes(%s)' % str.__repr__(self) | |||
|
394 | ||||
|
395 | ||||
|
396 | def get_mixed_type_key(obj): | |||
|
397 | """Return a key suitable for sorting between networks and addresses. | |||
|
398 | ||||
|
399 | Address and Network objects are not sortable by default; they're | |||
|
400 | fundamentally different so the expression | |||
|
401 | ||||
|
402 | IPv4Address('1.1.1.1') <= IPv4Network('1.1.1.1/24') | |||
|
403 | ||||
|
404 | doesn't make any sense. There are some times however, where you may wish | |||
|
405 | to have ipaddr sort these for you anyway. If you need to do this, you | |||
|
406 | can use this function as the key= argument to sorted(). | |||
|
407 | ||||
|
408 | Args: | |||
|
409 | obj: either a Network or Address object. | |||
|
410 | Returns: | |||
|
411 | appropriate key. | |||
|
412 | ||||
|
413 | """ | |||
|
414 | if isinstance(obj, _BaseNet): | |||
|
415 | return obj._get_networks_key() | |||
|
416 | elif isinstance(obj, _BaseIP): | |||
|
417 | return obj._get_address_key() | |||
|
418 | return NotImplemented | |||
|
419 | ||||
|
420 | ||||
|
421 | class _IPAddrBase(object): | |||
|
422 | ||||
|
423 | """The mother class.""" | |||
|
424 | ||||
|
425 | def __index__(self): | |||
|
426 | return self._ip | |||
|
427 | ||||
|
428 | def __int__(self): | |||
|
429 | return self._ip | |||
|
430 | ||||
|
431 | def __hex__(self): | |||
|
432 | return hex(self._ip) | |||
|
433 | ||||
|
434 | @property | |||
|
435 | def exploded(self): | |||
|
436 | """Return the longhand version of the IP address as a string.""" | |||
|
437 | return self._explode_shorthand_ip_string() | |||
|
438 | ||||
|
439 | @property | |||
|
440 | def compressed(self): | |||
|
441 | """Return the shorthand version of the IP address as a string.""" | |||
|
442 | return str(self) | |||
|
443 | ||||
|
444 | ||||
|
445 | class _BaseIP(_IPAddrBase): | |||
|
446 | ||||
|
447 | """A generic IP object. | |||
|
448 | ||||
|
449 | This IP class contains the version independent methods which are | |||
|
450 | used by single IP addresses. | |||
|
451 | ||||
|
452 | """ | |||
|
453 | ||||
|
454 | def __eq__(self, other): | |||
|
455 | try: | |||
|
456 | return (self._ip == other._ip | |||
|
457 | and self._version == other._version) | |||
|
458 | except AttributeError: | |||
|
459 | return NotImplemented | |||
|
460 | ||||
|
461 | def __ne__(self, other): | |||
|
462 | eq = self.__eq__(other) | |||
|
463 | if eq is NotImplemented: | |||
|
464 | return NotImplemented | |||
|
465 | return not eq | |||
|
466 | ||||
|
467 | def __le__(self, other): | |||
|
468 | gt = self.__gt__(other) | |||
|
469 | if gt is NotImplemented: | |||
|
470 | return NotImplemented | |||
|
471 | return not gt | |||
|
472 | ||||
|
473 | def __ge__(self, other): | |||
|
474 | lt = self.__lt__(other) | |||
|
475 | if lt is NotImplemented: | |||
|
476 | return NotImplemented | |||
|
477 | return not lt | |||
|
478 | ||||
|
479 | def __lt__(self, other): | |||
|
480 | if self._version != other._version: | |||
|
481 | raise TypeError('%s and %s are not of the same version' % ( | |||
|
482 | str(self), str(other))) | |||
|
483 | if not isinstance(other, _BaseIP): | |||
|
484 | raise TypeError('%s and %s are not of the same type' % ( | |||
|
485 | str(self), str(other))) | |||
|
486 | if self._ip != other._ip: | |||
|
487 | return self._ip < other._ip | |||
|
488 | return False | |||
|
489 | ||||
|
490 | def __gt__(self, other): | |||
|
491 | if self._version != other._version: | |||
|
492 | raise TypeError('%s and %s are not of the same version' % ( | |||
|
493 | str(self), str(other))) | |||
|
494 | if not isinstance(other, _BaseIP): | |||
|
495 | raise TypeError('%s and %s are not of the same type' % ( | |||
|
496 | str(self), str(other))) | |||
|
497 | if self._ip != other._ip: | |||
|
498 | return self._ip > other._ip | |||
|
499 | return False | |||
|
500 | ||||
|
501 | # Shorthand for Integer addition and subtraction. This is not | |||
|
502 | # meant to ever support addition/subtraction of addresses. | |||
|
503 | def __add__(self, other): | |||
|
504 | if not isinstance(other, int): | |||
|
505 | return NotImplemented | |||
|
506 | return IPAddress(int(self) + other, version=self._version) | |||
|
507 | ||||
|
508 | def __sub__(self, other): | |||
|
509 | if not isinstance(other, int): | |||
|
510 | return NotImplemented | |||
|
511 | return IPAddress(int(self) - other, version=self._version) | |||
|
512 | ||||
|
513 | def __repr__(self): | |||
|
514 | return '%s(%r)' % (self.__class__.__name__, str(self)) | |||
|
515 | ||||
|
516 | def __str__(self): | |||
|
517 | return '%s' % self._string_from_ip_int(self._ip) | |||
|
518 | ||||
|
519 | def __hash__(self): | |||
|
520 | return hash(hex(long(self._ip))) | |||
|
521 | ||||
|
522 | def _get_address_key(self): | |||
|
523 | return (self._version, self) | |||
|
524 | ||||
|
525 | @property | |||
|
526 | def version(self): | |||
|
527 | raise NotImplementedError('BaseIP has no version') | |||
|
528 | ||||
|
529 | ||||
|
530 | class _BaseNet(_IPAddrBase): | |||
|
531 | ||||
|
532 | """A generic IP object. | |||
|
533 | ||||
|
534 | This IP class contains the version independent methods which are | |||
|
535 | used by networks. | |||
|
536 | ||||
|
537 | """ | |||
|
538 | ||||
|
539 | def __init__(self, address): | |||
|
540 | self._cache = {} | |||
|
541 | ||||
|
542 | def __repr__(self): | |||
|
543 | return '%s(%r)' % (self.__class__.__name__, str(self)) | |||
|
544 | ||||
|
545 | def iterhosts(self): | |||
|
546 | """Generate Iterator over usable hosts in a network. | |||
|
547 | ||||
|
548 | This is like __iter__ except it doesn't return the network | |||
|
549 | or broadcast addresses. | |||
|
550 | ||||
|
551 | """ | |||
|
552 | cur = int(self.network) + 1 | |||
|
553 | bcast = int(self.broadcast) - 1 | |||
|
554 | while cur <= bcast: | |||
|
555 | cur += 1 | |||
|
556 | yield IPAddress(cur - 1, version=self._version) | |||
|
557 | ||||
|
558 | def __iter__(self): | |||
|
559 | cur = int(self.network) | |||
|
560 | bcast = int(self.broadcast) | |||
|
561 | while cur <= bcast: | |||
|
562 | cur += 1 | |||
|
563 | yield IPAddress(cur - 1, version=self._version) | |||
|
564 | ||||
|
565 | def __getitem__(self, n): | |||
|
566 | network = int(self.network) | |||
|
567 | broadcast = int(self.broadcast) | |||
|
568 | if n >= 0: | |||
|
569 | if network + n > broadcast: | |||
|
570 | raise IndexError | |||
|
571 | return IPAddress(network + n, version=self._version) | |||
|
572 | else: | |||
|
573 | n += 1 | |||
|
574 | if broadcast + n < network: | |||
|
575 | raise IndexError | |||
|
576 | return IPAddress(broadcast + n, version=self._version) | |||
|
577 | ||||
|
578 | def __lt__(self, other): | |||
|
579 | if self._version != other._version: | |||
|
580 | raise TypeError('%s and %s are not of the same version' % ( | |||
|
581 | str(self), str(other))) | |||
|
582 | if not isinstance(other, _BaseNet): | |||
|
583 | raise TypeError('%s and %s are not of the same type' % ( | |||
|
584 | str(self), str(other))) | |||
|
585 | if self.network != other.network: | |||
|
586 | return self.network < other.network | |||
|
587 | if self.netmask != other.netmask: | |||
|
588 | return self.netmask < other.netmask | |||
|
589 | return False | |||
|
590 | ||||
|
591 | def __gt__(self, other): | |||
|
592 | if self._version != other._version: | |||
|
593 | raise TypeError('%s and %s are not of the same version' % ( | |||
|
594 | str(self), str(other))) | |||
|
595 | if not isinstance(other, _BaseNet): | |||
|
596 | raise TypeError('%s and %s are not of the same type' % ( | |||
|
597 | str(self), str(other))) | |||
|
598 | if self.network != other.network: | |||
|
599 | return self.network > other.network | |||
|
600 | if self.netmask != other.netmask: | |||
|
601 | return self.netmask > other.netmask | |||
|
602 | return False | |||
|
603 | ||||
|
604 | def __le__(self, other): | |||
|
605 | gt = self.__gt__(other) | |||
|
606 | if gt is NotImplemented: | |||
|
607 | return NotImplemented | |||
|
608 | return not gt | |||
|
609 | ||||
|
610 | def __ge__(self, other): | |||
|
611 | lt = self.__lt__(other) | |||
|
612 | if lt is NotImplemented: | |||
|
613 | return NotImplemented | |||
|
614 | return not lt | |||
|
615 | ||||
|
616 | def __eq__(self, other): | |||
|
617 | try: | |||
|
618 | return (self._version == other._version | |||
|
619 | and self.network == other.network | |||
|
620 | and int(self.netmask) == int(other.netmask)) | |||
|
621 | except AttributeError: | |||
|
622 | if isinstance(other, _BaseIP): | |||
|
623 | return (self._version == other._version | |||
|
624 | and self._ip == other._ip) | |||
|
625 | ||||
|
626 | def __ne__(self, other): | |||
|
627 | eq = self.__eq__(other) | |||
|
628 | if eq is NotImplemented: | |||
|
629 | return NotImplemented | |||
|
630 | return not eq | |||
|
631 | ||||
|
632 | def __str__(self): | |||
|
633 | return '%s/%s' % (str(self.ip), | |||
|
634 | str(self._prefixlen)) | |||
|
635 | ||||
|
636 | def __hash__(self): | |||
|
637 | return hash(int(self.network) ^ int(self.netmask)) | |||
|
638 | ||||
|
639 | def __contains__(self, other): | |||
|
640 | # always false if one is v4 and the other is v6. | |||
|
641 | if self._version != other._version: | |||
|
642 | return False | |||
|
643 | # dealing with another network. | |||
|
644 | if isinstance(other, _BaseNet): | |||
|
645 | return (self.network <= other.network and | |||
|
646 | self.broadcast >= other.broadcast) | |||
|
647 | # dealing with another address | |||
|
648 | else: | |||
|
649 | return (int(self.network) <= int(other._ip) <= | |||
|
650 | int(self.broadcast)) | |||
|
651 | ||||
|
652 | def overlaps(self, other): | |||
|
653 | """Tell if self is partly contained in other.""" | |||
|
654 | return self.network in other or self.broadcast in other or ( | |||
|
655 | other.network in self or other.broadcast in self) | |||
|
656 | ||||
|
657 | @property | |||
|
658 | def network(self): | |||
|
659 | x = self._cache.get('network') | |||
|
660 | if x is None: | |||
|
661 | x = IPAddress(self._ip & int(self.netmask), version=self._version) | |||
|
662 | self._cache['network'] = x | |||
|
663 | return x | |||
|
664 | ||||
|
665 | @property | |||
|
666 | def broadcast(self): | |||
|
667 | x = self._cache.get('broadcast') | |||
|
668 | if x is None: | |||
|
669 | x = IPAddress(self._ip | int(self.hostmask), version=self._version) | |||
|
670 | self._cache['broadcast'] = x | |||
|
671 | return x | |||
|
672 | ||||
|
673 | @property | |||
|
674 | def hostmask(self): | |||
|
675 | x = self._cache.get('hostmask') | |||
|
676 | if x is None: | |||
|
677 | x = IPAddress(int(self.netmask) ^ self._ALL_ONES, | |||
|
678 | version=self._version) | |||
|
679 | self._cache['hostmask'] = x | |||
|
680 | return x | |||
|
681 | ||||
|
682 | @property | |||
|
683 | def with_prefixlen(self): | |||
|
684 | return '%s/%d' % (str(self.ip), self._prefixlen) | |||
|
685 | ||||
|
686 | @property | |||
|
687 | def with_netmask(self): | |||
|
688 | return '%s/%s' % (str(self.ip), str(self.netmask)) | |||
|
689 | ||||
|
690 | @property | |||
|
691 | def with_hostmask(self): | |||
|
692 | return '%s/%s' % (str(self.ip), str(self.hostmask)) | |||
|
693 | ||||
|
694 | @property | |||
|
695 | def numhosts(self): | |||
|
696 | """Number of hosts in the current subnet.""" | |||
|
697 | return int(self.broadcast) - int(self.network) + 1 | |||
|
698 | ||||
|
699 | @property | |||
|
700 | def version(self): | |||
|
701 | raise NotImplementedError('BaseNet has no version') | |||
|
702 | ||||
|
703 | @property | |||
|
704 | def prefixlen(self): | |||
|
705 | return self._prefixlen | |||
|
706 | ||||
|
707 | def address_exclude(self, other): | |||
|
708 | """Remove an address from a larger block. | |||
|
709 | ||||
|
710 | For example: | |||
|
711 | ||||
|
712 | addr1 = IPNetwork('10.1.1.0/24') | |||
|
713 | addr2 = IPNetwork('10.1.1.0/26') | |||
|
714 | addr1.address_exclude(addr2) = | |||
|
715 | [IPNetwork('10.1.1.64/26'), IPNetwork('10.1.1.128/25')] | |||
|
716 | ||||
|
717 | or IPv6: | |||
|
718 | ||||
|
719 | addr1 = IPNetwork('::1/32') | |||
|
720 | addr2 = IPNetwork('::1/128') | |||
|
721 | addr1.address_exclude(addr2) = [IPNetwork('::0/128'), | |||
|
722 | IPNetwork('::2/127'), | |||
|
723 | IPNetwork('::4/126'), | |||
|
724 | IPNetwork('::8/125'), | |||
|
725 | ... | |||
|
726 | IPNetwork('0:0:8000::/33')] | |||
|
727 | ||||
|
728 | Args: | |||
|
729 | other: An IPvXNetwork object of the same type. | |||
|
730 | ||||
|
731 | Returns: | |||
|
732 | A sorted list of IPvXNetwork objects addresses which is self | |||
|
733 | minus other. | |||
|
734 | ||||
|
735 | Raises: | |||
|
736 | TypeError: If self and other are of difffering address | |||
|
737 | versions, or if other is not a network object. | |||
|
738 | ValueError: If other is not completely contained by self. | |||
|
739 | ||||
|
740 | """ | |||
|
741 | if not self._version == other._version: | |||
|
742 | raise TypeError("%s and %s are not of the same version" % ( | |||
|
743 | str(self), str(other))) | |||
|
744 | ||||
|
745 | if not isinstance(other, _BaseNet): | |||
|
746 | raise TypeError("%s is not a network object" % str(other)) | |||
|
747 | ||||
|
748 | if other not in self: | |||
|
749 | raise ValueError('%s not contained in %s' % (str(other), | |||
|
750 | str(self))) | |||
|
751 | if other == self: | |||
|
752 | return [] | |||
|
753 | ||||
|
754 | ret_addrs = [] | |||
|
755 | ||||
|
756 | # Make sure we're comparing the network of other. | |||
|
757 | other = IPNetwork('%s/%s' % (str(other.network), str(other.prefixlen)), | |||
|
758 | version=other._version) | |||
|
759 | ||||
|
760 | s1, s2 = self.subnet() | |||
|
761 | while s1 != other and s2 != other: | |||
|
762 | if other in s1: | |||
|
763 | ret_addrs.append(s2) | |||
|
764 | s1, s2 = s1.subnet() | |||
|
765 | elif other in s2: | |||
|
766 | ret_addrs.append(s1) | |||
|
767 | s1, s2 = s2.subnet() | |||
|
768 | else: | |||
|
769 | # If we got here, there's a bug somewhere. | |||
|
770 | assert True == False, ('Error performing exclusion: ' | |||
|
771 | 's1: %s s2: %s other: %s' % | |||
|
772 | (str(s1), str(s2), str(other))) | |||
|
773 | if s1 == other: | |||
|
774 | ret_addrs.append(s2) | |||
|
775 | elif s2 == other: | |||
|
776 | ret_addrs.append(s1) | |||
|
777 | else: | |||
|
778 | # If we got here, there's a bug somewhere. | |||
|
779 | assert True == False, ('Error performing exclusion: ' | |||
|
780 | 's1: %s s2: %s other: %s' % | |||
|
781 | (str(s1), str(s2), str(other))) | |||
|
782 | ||||
|
783 | return sorted(ret_addrs, key=_BaseNet._get_networks_key) | |||
|
784 | ||||
|
785 | def compare_networks(self, other): | |||
|
786 | """Compare two IP objects. | |||
|
787 | ||||
|
788 | This is only concerned about the comparison of the integer | |||
|
789 | representation of the network addresses. This means that the | |||
|
790 | host bits aren't considered at all in this method. If you want | |||
|
791 | to compare host bits, you can easily enough do a | |||
|
792 | 'HostA._ip < HostB._ip' | |||
|
793 | ||||
|
794 | Args: | |||
|
795 | other: An IP object. | |||
|
796 | ||||
|
797 | Returns: | |||
|
798 | If the IP versions of self and other are the same, returns: | |||
|
799 | ||||
|
800 | -1 if self < other: | |||
|
801 | eg: IPv4('1.1.1.0/24') < IPv4('1.1.2.0/24') | |||
|
802 | IPv6('1080::200C:417A') < IPv6('1080::200B:417B') | |||
|
803 | 0 if self == other | |||
|
804 | eg: IPv4('1.1.1.1/24') == IPv4('1.1.1.2/24') | |||
|
805 | IPv6('1080::200C:417A/96') == IPv6('1080::200C:417B/96') | |||
|
806 | 1 if self > other | |||
|
807 | eg: IPv4('1.1.1.0/24') > IPv4('1.1.0.0/24') | |||
|
808 | IPv6('1080::1:200C:417A/112') > | |||
|
809 | IPv6('1080::0:200C:417A/112') | |||
|
810 | ||||
|
811 | If the IP versions of self and other are different, returns: | |||
|
812 | ||||
|
813 | -1 if self._version < other._version | |||
|
814 | eg: IPv4('10.0.0.1/24') < IPv6('::1/128') | |||
|
815 | 1 if self._version > other._version | |||
|
816 | eg: IPv6('::1/128') > IPv4('255.255.255.0/24') | |||
|
817 | ||||
|
818 | """ | |||
|
819 | if self._version < other._version: | |||
|
820 | return -1 | |||
|
821 | if self._version > other._version: | |||
|
822 | return 1 | |||
|
823 | # self._version == other._version below here: | |||
|
824 | if self.network < other.network: | |||
|
825 | return -1 | |||
|
826 | if self.network > other.network: | |||
|
827 | return 1 | |||
|
828 | # self.network == other.network below here: | |||
|
829 | if self.netmask < other.netmask: | |||
|
830 | return -1 | |||
|
831 | if self.netmask > other.netmask: | |||
|
832 | return 1 | |||
|
833 | # self.network == other.network and self.netmask == other.netmask | |||
|
834 | return 0 | |||
|
835 | ||||
|
836 | def _get_networks_key(self): | |||
|
837 | """Network-only key function. | |||
|
838 | ||||
|
839 | Returns an object that identifies this address' network and | |||
|
840 | netmask. This function is a suitable "key" argument for sorted() | |||
|
841 | and list.sort(). | |||
|
842 | ||||
|
843 | """ | |||
|
844 | return (self._version, self.network, self.netmask) | |||
|
845 | ||||
|
846 | def _ip_int_from_prefix(self, prefixlen=None): | |||
|
847 | """Turn the prefix length netmask into a int for comparison. | |||
|
848 | ||||
|
849 | Args: | |||
|
850 | prefixlen: An integer, the prefix length. | |||
|
851 | ||||
|
852 | Returns: | |||
|
853 | An integer. | |||
|
854 | ||||
|
855 | """ | |||
|
856 | if not prefixlen and prefixlen != 0: | |||
|
857 | prefixlen = self._prefixlen | |||
|
858 | return self._ALL_ONES ^ (self._ALL_ONES >> prefixlen) | |||
|
859 | ||||
|
860 | def _prefix_from_ip_int(self, ip_int, mask=32): | |||
|
861 | """Return prefix length from the decimal netmask. | |||
|
862 | ||||
|
863 | Args: | |||
|
864 | ip_int: An integer, the IP address. | |||
|
865 | mask: The netmask. Defaults to 32. | |||
|
866 | ||||
|
867 | Returns: | |||
|
868 | An integer, the prefix length. | |||
|
869 | ||||
|
870 | """ | |||
|
871 | while mask: | |||
|
872 | if ip_int & 1 == 1: | |||
|
873 | break | |||
|
874 | ip_int >>= 1 | |||
|
875 | mask -= 1 | |||
|
876 | ||||
|
877 | return mask | |||
|
878 | ||||
|
879 | def _ip_string_from_prefix(self, prefixlen=None): | |||
|
880 | """Turn a prefix length into a dotted decimal string. | |||
|
881 | ||||
|
882 | Args: | |||
|
883 | prefixlen: An integer, the netmask prefix length. | |||
|
884 | ||||
|
885 | Returns: | |||
|
886 | A string, the dotted decimal netmask string. | |||
|
887 | ||||
|
888 | """ | |||
|
889 | if not prefixlen: | |||
|
890 | prefixlen = self._prefixlen | |||
|
891 | return self._string_from_ip_int(self._ip_int_from_prefix(prefixlen)) | |||
|
892 | ||||
|
893 | def iter_subnets(self, prefixlen_diff=1, new_prefix=None): | |||
|
894 | """The subnets which join to make the current subnet. | |||
|
895 | ||||
|
896 | In the case that self contains only one IP | |||
|
897 | (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 | |||
|
898 | for IPv6), return a list with just ourself. | |||
|
899 | ||||
|
900 | Args: | |||
|
901 | prefixlen_diff: An integer, the amount the prefix length | |||
|
902 | should be increased by. This should not be set if | |||
|
903 | new_prefix is also set. | |||
|
904 | new_prefix: The desired new prefix length. This must be a | |||
|
905 | larger number (smaller prefix) than the existing prefix. | |||
|
906 | This should not be set if prefixlen_diff is also set. | |||
|
907 | ||||
|
908 | Returns: | |||
|
909 | An iterator of IPv(4|6) objects. | |||
|
910 | ||||
|
911 | Raises: | |||
|
912 | ValueError: The prefixlen_diff is too small or too large. | |||
|
913 | OR | |||
|
914 | prefixlen_diff and new_prefix are both set or new_prefix | |||
|
915 | is a smaller number than the current prefix (smaller | |||
|
916 | number means a larger network) | |||
|
917 | ||||
|
918 | """ | |||
|
919 | if self._prefixlen == self._max_prefixlen: | |||
|
920 | yield self | |||
|
921 | return | |||
|
922 | ||||
|
923 | if new_prefix is not None: | |||
|
924 | if new_prefix < self._prefixlen: | |||
|
925 | raise ValueError('new prefix must be longer') | |||
|
926 | if prefixlen_diff != 1: | |||
|
927 | raise ValueError('cannot set prefixlen_diff and new_prefix') | |||
|
928 | prefixlen_diff = new_prefix - self._prefixlen | |||
|
929 | ||||
|
930 | if prefixlen_diff < 0: | |||
|
931 | raise ValueError('prefix length diff must be > 0') | |||
|
932 | new_prefixlen = self._prefixlen + prefixlen_diff | |||
|
933 | ||||
|
934 | if not self._is_valid_netmask(str(new_prefixlen)): | |||
|
935 | raise ValueError( | |||
|
936 | 'prefix length diff %d is invalid for netblock %s' % ( | |||
|
937 | new_prefixlen, str(self))) | |||
|
938 | ||||
|
939 | first = IPNetwork('%s/%s' % (str(self.network), | |||
|
940 | str(self._prefixlen + prefixlen_diff)), | |||
|
941 | version=self._version) | |||
|
942 | ||||
|
943 | yield first | |||
|
944 | current = first | |||
|
945 | while True: | |||
|
946 | broadcast = current.broadcast | |||
|
947 | if broadcast == self.broadcast: | |||
|
948 | return | |||
|
949 | new_addr = IPAddress(int(broadcast) + 1, version=self._version) | |||
|
950 | current = IPNetwork('%s/%s' % (str(new_addr), str(new_prefixlen)), | |||
|
951 | version=self._version) | |||
|
952 | ||||
|
953 | yield current | |||
|
954 | ||||
|
955 | def masked(self): | |||
|
956 | """Return the network object with the host bits masked out.""" | |||
|
957 | return IPNetwork('%s/%d' % (self.network, self._prefixlen), | |||
|
958 | version=self._version) | |||
|
959 | ||||
|
960 | def subnet(self, prefixlen_diff=1, new_prefix=None): | |||
|
961 | """Return a list of subnets, rather than an iterator.""" | |||
|
962 | return list(self.iter_subnets(prefixlen_diff, new_prefix)) | |||
|
963 | ||||
|
964 | def supernet(self, prefixlen_diff=1, new_prefix=None): | |||
|
965 | """The supernet containing the current network. | |||
|
966 | ||||
|
967 | Args: | |||
|
968 | prefixlen_diff: An integer, the amount the prefix length of | |||
|
969 | the network should be decreased by. For example, given a | |||
|
970 | /24 network and a prefixlen_diff of 3, a supernet with a | |||
|
971 | /21 netmask is returned. | |||
|
972 | ||||
|
973 | Returns: | |||
|
974 | An IPv4 network object. | |||
|
975 | ||||
|
976 | Raises: | |||
|
977 | ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have a | |||
|
978 | negative prefix length. | |||
|
979 | OR | |||
|
980 | If prefixlen_diff and new_prefix are both set or new_prefix is a | |||
|
981 | larger number than the current prefix (larger number means a | |||
|
982 | smaller network) | |||
|
983 | ||||
|
984 | """ | |||
|
985 | if self._prefixlen == 0: | |||
|
986 | return self | |||
|
987 | ||||
|
988 | if new_prefix is not None: | |||
|
989 | if new_prefix > self._prefixlen: | |||
|
990 | raise ValueError('new prefix must be shorter') | |||
|
991 | if prefixlen_diff != 1: | |||
|
992 | raise ValueError('cannot set prefixlen_diff and new_prefix') | |||
|
993 | prefixlen_diff = self._prefixlen - new_prefix | |||
|
994 | ||||
|
995 | if self.prefixlen - prefixlen_diff < 0: | |||
|
996 | raise ValueError( | |||
|
997 | 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % | |||
|
998 | (self.prefixlen, prefixlen_diff)) | |||
|
999 | return IPNetwork('%s/%s' % (str(self.network), | |||
|
1000 | str(self.prefixlen - prefixlen_diff)), | |||
|
1001 | version=self._version) | |||
|
1002 | ||||
|
1003 | # backwards compatibility | |||
|
1004 | Subnet = subnet | |||
|
1005 | Supernet = supernet | |||
|
1006 | AddressExclude = address_exclude | |||
|
1007 | CompareNetworks = compare_networks | |||
|
1008 | Contains = __contains__ | |||
|
1009 | ||||
|
1010 | ||||
|
1011 | class _BaseV4(object): | |||
|
1012 | ||||
|
1013 | """Base IPv4 object. | |||
|
1014 | ||||
|
1015 | The following methods are used by IPv4 objects in both single IP | |||
|
1016 | addresses and networks. | |||
|
1017 | ||||
|
1018 | """ | |||
|
1019 | ||||
|
1020 | # Equivalent to 255.255.255.255 or 32 bits of 1's. | |||
|
1021 | _ALL_ONES = (2 ** IPV4LENGTH) - 1 | |||
|
1022 | _DECIMAL_DIGITS = frozenset('0123456789') | |||
|
1023 | ||||
|
1024 | def __init__(self, address): | |||
|
1025 | self._version = 4 | |||
|
1026 | self._max_prefixlen = IPV4LENGTH | |||
|
1027 | ||||
|
1028 | def _explode_shorthand_ip_string(self): | |||
|
1029 | return str(self) | |||
|
1030 | ||||
|
1031 | def _ip_int_from_string(self, ip_str): | |||
|
1032 | """Turn the given IP string into an integer for comparison. | |||
|
1033 | ||||
|
1034 | Args: | |||
|
1035 | ip_str: A string, the IP ip_str. | |||
|
1036 | ||||
|
1037 | Returns: | |||
|
1038 | The IP ip_str as an integer. | |||
|
1039 | ||||
|
1040 | Raises: | |||
|
1041 | AddressValueError: if ip_str isn't a valid IPv4 Address. | |||
|
1042 | ||||
|
1043 | """ | |||
|
1044 | octets = ip_str.split('.') | |||
|
1045 | if len(octets) != 4: | |||
|
1046 | raise AddressValueError(ip_str) | |||
|
1047 | ||||
|
1048 | packed_ip = 0 | |||
|
1049 | for oc in octets: | |||
|
1050 | try: | |||
|
1051 | packed_ip = (packed_ip << 8) | self._parse_octet(oc) | |||
|
1052 | except ValueError: | |||
|
1053 | raise AddressValueError(ip_str) | |||
|
1054 | return packed_ip | |||
|
1055 | ||||
|
1056 | def _parse_octet(self, octet_str): | |||
|
1057 | """Convert a decimal octet into an integer. | |||
|
1058 | ||||
|
1059 | Args: | |||
|
1060 | octet_str: A string, the number to parse. | |||
|
1061 | ||||
|
1062 | Returns: | |||
|
1063 | The octet as an integer. | |||
|
1064 | ||||
|
1065 | Raises: | |||
|
1066 | ValueError: if the octet isn't strictly a decimal from [0..255]. | |||
|
1067 | ||||
|
1068 | """ | |||
|
1069 | # Whitelist the characters, since int() allows a lot of bizarre stuff. | |||
|
1070 | if not self._DECIMAL_DIGITS.issuperset(octet_str): | |||
|
1071 | raise ValueError | |||
|
1072 | octet_int = int(octet_str, 10) | |||
|
1073 | # Disallow leading zeroes, because no clear standard exists on | |||
|
1074 | # whether these should be interpreted as decimal or octal. | |||
|
1075 | if octet_int > 255 or (octet_str[0] == '0' and len(octet_str) > 1): | |||
|
1076 | raise ValueError | |||
|
1077 | return octet_int | |||
|
1078 | ||||
|
1079 | def _string_from_ip_int(self, ip_int): | |||
|
1080 | """Turns a 32-bit integer into dotted decimal notation. | |||
|
1081 | ||||
|
1082 | Args: | |||
|
1083 | ip_int: An integer, the IP address. | |||
|
1084 | ||||
|
1085 | Returns: | |||
|
1086 | The IP address as a string in dotted decimal notation. | |||
|
1087 | ||||
|
1088 | """ | |||
|
1089 | octets = [] | |||
|
1090 | for _ in xrange(4): | |||
|
1091 | octets.insert(0, str(ip_int & 0xFF)) | |||
|
1092 | ip_int >>= 8 | |||
|
1093 | return '.'.join(octets) | |||
|
1094 | ||||
|
1095 | @property | |||
|
1096 | def max_prefixlen(self): | |||
|
1097 | return self._max_prefixlen | |||
|
1098 | ||||
|
1099 | @property | |||
|
1100 | def packed(self): | |||
|
1101 | """The binary representation of this address.""" | |||
|
1102 | return v4_int_to_packed(self._ip) | |||
|
1103 | ||||
|
1104 | @property | |||
|
1105 | def version(self): | |||
|
1106 | return self._version | |||
|
1107 | ||||
|
1108 | @property | |||
|
1109 | def is_reserved(self): | |||
|
1110 | """Test if the address is otherwise IETF reserved. | |||
|
1111 | ||||
|
1112 | Returns: | |||
|
1113 | A boolean, True if the address is within the | |||
|
1114 | reserved IPv4 Network range. | |||
|
1115 | ||||
|
1116 | """ | |||
|
1117 | return self in IPv4Network('240.0.0.0/4') | |||
|
1118 | ||||
|
1119 | @property | |||
|
1120 | def is_private(self): | |||
|
1121 | """Test if this address is allocated for private networks. | |||
|
1122 | ||||
|
1123 | Returns: | |||
|
1124 | A boolean, True if the address is reserved per RFC 1918. | |||
|
1125 | ||||
|
1126 | """ | |||
|
1127 | return (self in IPv4Network('10.0.0.0/8') or | |||
|
1128 | self in IPv4Network('172.16.0.0/12') or | |||
|
1129 | self in IPv4Network('192.168.0.0/16')) | |||
|
1130 | ||||
|
1131 | @property | |||
|
1132 | def is_multicast(self): | |||
|
1133 | """Test if the address is reserved for multicast use. | |||
|
1134 | ||||
|
1135 | Returns: | |||
|
1136 | A boolean, True if the address is multicast. | |||
|
1137 | See RFC 3171 for details. | |||
|
1138 | ||||
|
1139 | """ | |||
|
1140 | return self in IPv4Network('224.0.0.0/4') | |||
|
1141 | ||||
|
1142 | @property | |||
|
1143 | def is_unspecified(self): | |||
|
1144 | """Test if the address is unspecified. | |||
|
1145 | ||||
|
1146 | Returns: | |||
|
1147 | A boolean, True if this is the unspecified address as defined in | |||
|
1148 | RFC 5735 3. | |||
|
1149 | ||||
|
1150 | """ | |||
|
1151 | return self in IPv4Network('0.0.0.0') | |||
|
1152 | ||||
|
1153 | @property | |||
|
1154 | def is_loopback(self): | |||
|
1155 | """Test if the address is a loopback address. | |||
|
1156 | ||||
|
1157 | Returns: | |||
|
1158 | A boolean, True if the address is a loopback per RFC 3330. | |||
|
1159 | ||||
|
1160 | """ | |||
|
1161 | return self in IPv4Network('127.0.0.0/8') | |||
|
1162 | ||||
|
1163 | @property | |||
|
1164 | def is_link_local(self): | |||
|
1165 | """Test if the address is reserved for link-local. | |||
|
1166 | ||||
|
1167 | Returns: | |||
|
1168 | A boolean, True if the address is link-local per RFC 3927. | |||
|
1169 | ||||
|
1170 | """ | |||
|
1171 | return self in IPv4Network('169.254.0.0/16') | |||
|
1172 | ||||
|
1173 | ||||
|
1174 | class IPv4Address(_BaseV4, _BaseIP): | |||
|
1175 | ||||
|
1176 | """Represent and manipulate single IPv4 Addresses.""" | |||
|
1177 | ||||
|
1178 | def __init__(self, address): | |||
|
1179 | ||||
|
1180 | """ | |||
|
1181 | Args: | |||
|
1182 | address: A string or integer representing the IP | |||
|
1183 | '192.168.1.1' | |||
|
1184 | ||||
|
1185 | Additionally, an integer can be passed, so | |||
|
1186 | IPv4Address('192.168.1.1') == IPv4Address(3232235777). | |||
|
1187 | or, more generally | |||
|
1188 | IPv4Address(int(IPv4Address('192.168.1.1'))) == | |||
|
1189 | IPv4Address('192.168.1.1') | |||
|
1190 | ||||
|
1191 | Raises: | |||
|
1192 | AddressValueError: If ipaddr isn't a valid IPv4 address. | |||
|
1193 | ||||
|
1194 | """ | |||
|
1195 | _BaseV4.__init__(self, address) | |||
|
1196 | ||||
|
1197 | # Efficient constructor from integer. | |||
|
1198 | if isinstance(address, (int, long)): | |||
|
1199 | self._ip = address | |||
|
1200 | if address < 0 or address > self._ALL_ONES: | |||
|
1201 | raise AddressValueError(address) | |||
|
1202 | return | |||
|
1203 | ||||
|
1204 | # Constructing from a packed address | |||
|
1205 | if isinstance(address, Bytes): | |||
|
1206 | try: | |||
|
1207 | self._ip, = struct.unpack('!I', address) | |||
|
1208 | except struct.error: | |||
|
1209 | raise AddressValueError(address) # Wrong length. | |||
|
1210 | return | |||
|
1211 | ||||
|
1212 | # Assume input argument to be string or any object representation | |||
|
1213 | # which converts into a formatted IP string. | |||
|
1214 | addr_str = str(address) | |||
|
1215 | self._ip = self._ip_int_from_string(addr_str) | |||
|
1216 | ||||
|
1217 | ||||
|
1218 | class IPv4Network(_BaseV4, _BaseNet): | |||
|
1219 | ||||
|
1220 | """This class represents and manipulates 32-bit IPv4 networks. | |||
|
1221 | ||||
|
1222 | Attributes: [examples for IPv4Network('1.2.3.4/27')] | |||
|
1223 | ._ip: 16909060 | |||
|
1224 | .ip: IPv4Address('1.2.3.4') | |||
|
1225 | .network: IPv4Address('1.2.3.0') | |||
|
1226 | .hostmask: IPv4Address('0.0.0.31') | |||
|
1227 | .broadcast: IPv4Address('1.2.3.31') | |||
|
1228 | .netmask: IPv4Address('255.255.255.224') | |||
|
1229 | .prefixlen: 27 | |||
|
1230 | ||||
|
1231 | """ | |||
|
1232 | ||||
|
1233 | # the valid octets for host and netmasks. only useful for IPv4. | |||
|
1234 | _valid_mask_octets = set((255, 254, 252, 248, 240, 224, 192, 128, 0)) | |||
|
1235 | ||||
|
1236 | def __init__(self, address, strict=False): | |||
|
1237 | """Instantiate a new IPv4 network object. | |||
|
1238 | ||||
|
1239 | Args: | |||
|
1240 | address: A string or integer representing the IP [& network]. | |||
|
1241 | '192.168.1.1/24' | |||
|
1242 | '192.168.1.1/255.255.255.0' | |||
|
1243 | '192.168.1.1/0.0.0.255' | |||
|
1244 | are all functionally the same in IPv4. Similarly, | |||
|
1245 | '192.168.1.1' | |||
|
1246 | '192.168.1.1/255.255.255.255' | |||
|
1247 | '192.168.1.1/32' | |||
|
1248 | are also functionaly equivalent. That is to say, failing to | |||
|
1249 | provide a subnetmask will create an object with a mask of /32. | |||
|
1250 | ||||
|
1251 | If the mask (portion after the / in the argument) is given in | |||
|
1252 | dotted quad form, it is treated as a netmask if it starts with a | |||
|
1253 | non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it | |||
|
1254 | starts with a zero field (e.g. 0.255.255.255 == /8), with the | |||
|
1255 | single exception of an all-zero mask which is treated as a | |||
|
1256 | netmask == /0. If no mask is given, a default of /32 is used. | |||
|
1257 | ||||
|
1258 | Additionally, an integer can be passed, so | |||
|
1259 | IPv4Network('192.168.1.1') == IPv4Network(3232235777). | |||
|
1260 | or, more generally | |||
|
1261 | IPv4Network(int(IPv4Network('192.168.1.1'))) == | |||
|
1262 | IPv4Network('192.168.1.1') | |||
|
1263 | ||||
|
1264 | strict: A boolean. If true, ensure that we have been passed | |||
|
1265 | A true network address, eg, 192.168.1.0/24 and not an | |||
|
1266 | IP address on a network, eg, 192.168.1.1/24. | |||
|
1267 | ||||
|
1268 | Raises: | |||
|
1269 | AddressValueError: If ipaddr isn't a valid IPv4 address. | |||
|
1270 | NetmaskValueError: If the netmask isn't valid for | |||
|
1271 | an IPv4 address. | |||
|
1272 | ValueError: If strict was True and a network address was not | |||
|
1273 | supplied. | |||
|
1274 | ||||
|
1275 | """ | |||
|
1276 | _BaseNet.__init__(self, address) | |||
|
1277 | _BaseV4.__init__(self, address) | |||
|
1278 | ||||
|
1279 | # Constructing from an integer or packed bytes. | |||
|
1280 | if isinstance(address, (int, long, Bytes)): | |||
|
1281 | self.ip = IPv4Address(address) | |||
|
1282 | self._ip = self.ip._ip | |||
|
1283 | self._prefixlen = self._max_prefixlen | |||
|
1284 | self.netmask = IPv4Address(self._ALL_ONES) | |||
|
1285 | return | |||
|
1286 | ||||
|
1287 | # Assume input argument to be string or any object representation | |||
|
1288 | # which converts into a formatted IP prefix string. | |||
|
1289 | addr = str(address).split('/') | |||
|
1290 | ||||
|
1291 | if len(addr) > 2: | |||
|
1292 | raise AddressValueError(address) | |||
|
1293 | ||||
|
1294 | self._ip = self._ip_int_from_string(addr[0]) | |||
|
1295 | self.ip = IPv4Address(self._ip) | |||
|
1296 | ||||
|
1297 | if len(addr) == 2: | |||
|
1298 | mask = addr[1].split('.') | |||
|
1299 | if len(mask) == 4: | |||
|
1300 | # We have dotted decimal netmask. | |||
|
1301 | if self._is_valid_netmask(addr[1]): | |||
|
1302 | self.netmask = IPv4Address(self._ip_int_from_string( | |||
|
1303 | addr[1])) | |||
|
1304 | elif self._is_hostmask(addr[1]): | |||
|
1305 | self.netmask = IPv4Address( | |||
|
1306 | self._ip_int_from_string(addr[1]) ^ self._ALL_ONES) | |||
|
1307 | else: | |||
|
1308 | raise NetmaskValueError('%s is not a valid netmask' | |||
|
1309 | % addr[1]) | |||
|
1310 | ||||
|
1311 | self._prefixlen = self._prefix_from_ip_int(int(self.netmask)) | |||
|
1312 | else: | |||
|
1313 | # We have a netmask in prefix length form. | |||
|
1314 | if not self._is_valid_netmask(addr[1]): | |||
|
1315 | raise NetmaskValueError(addr[1]) | |||
|
1316 | self._prefixlen = int(addr[1]) | |||
|
1317 | self.netmask = IPv4Address(self._ip_int_from_prefix( | |||
|
1318 | self._prefixlen)) | |||
|
1319 | else: | |||
|
1320 | self._prefixlen = self._max_prefixlen | |||
|
1321 | self.netmask = IPv4Address(self._ip_int_from_prefix( | |||
|
1322 | self._prefixlen)) | |||
|
1323 | if strict: | |||
|
1324 | if self.ip != self.network: | |||
|
1325 | raise ValueError('%s has host bits set' % | |||
|
1326 | self.ip) | |||
|
1327 | if self._prefixlen == (self._max_prefixlen - 1): | |||
|
1328 | self.iterhosts = self.__iter__ | |||
|
1329 | ||||
|
1330 | def _is_hostmask(self, ip_str): | |||
|
1331 | """Test if the IP string is a hostmask (rather than a netmask). | |||
|
1332 | ||||
|
1333 | Args: | |||
|
1334 | ip_str: A string, the potential hostmask. | |||
|
1335 | ||||
|
1336 | Returns: | |||
|
1337 | A boolean, True if the IP string is a hostmask. | |||
|
1338 | ||||
|
1339 | """ | |||
|
1340 | bits = ip_str.split('.') | |||
|
1341 | try: | |||
|
1342 | parts = [int(x) for x in bits if int(x) in self._valid_mask_octets] | |||
|
1343 | except ValueError: | |||
|
1344 | return False | |||
|
1345 | if len(parts) != len(bits): | |||
|
1346 | return False | |||
|
1347 | if parts[0] < parts[-1]: | |||
|
1348 | return True | |||
|
1349 | return False | |||
|
1350 | ||||
|
1351 | def _is_valid_netmask(self, netmask): | |||
|
1352 | """Verify that the netmask is valid. | |||
|
1353 | ||||
|
1354 | Args: | |||
|
1355 | netmask: A string, either a prefix or dotted decimal | |||
|
1356 | netmask. | |||
|
1357 | ||||
|
1358 | Returns: | |||
|
1359 | A boolean, True if the prefix represents a valid IPv4 | |||
|
1360 | netmask. | |||
|
1361 | ||||
|
1362 | """ | |||
|
1363 | mask = netmask.split('.') | |||
|
1364 | if len(mask) == 4: | |||
|
1365 | if [x for x in mask if int(x) not in self._valid_mask_octets]: | |||
|
1366 | return False | |||
|
1367 | if [y for idx, y in enumerate(mask) if idx > 0 and | |||
|
1368 | y > mask[idx - 1]]: | |||
|
1369 | return False | |||
|
1370 | return True | |||
|
1371 | try: | |||
|
1372 | netmask = int(netmask) | |||
|
1373 | except ValueError: | |||
|
1374 | return False | |||
|
1375 | return 0 <= netmask <= self._max_prefixlen | |||
|
1376 | ||||
|
1377 | # backwards compatibility | |||
|
1378 | IsRFC1918 = lambda self: self.is_private | |||
|
1379 | IsMulticast = lambda self: self.is_multicast | |||
|
1380 | IsLoopback = lambda self: self.is_loopback | |||
|
1381 | IsLinkLocal = lambda self: self.is_link_local | |||
|
1382 | ||||
|
1383 | ||||
|
1384 | class _BaseV6(object): | |||
|
1385 | ||||
|
1386 | """Base IPv6 object. | |||
|
1387 | ||||
|
1388 | The following methods are used by IPv6 objects in both single IP | |||
|
1389 | addresses and networks. | |||
|
1390 | ||||
|
1391 | """ | |||
|
1392 | ||||
|
1393 | _ALL_ONES = (2 ** IPV6LENGTH) - 1 | |||
|
1394 | _HEXTET_COUNT = 8 | |||
|
1395 | _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') | |||
|
1396 | ||||
|
1397 | def __init__(self, address): | |||
|
1398 | self._version = 6 | |||
|
1399 | self._max_prefixlen = IPV6LENGTH | |||
|
1400 | ||||
|
1401 | def _ip_int_from_string(self, ip_str): | |||
|
1402 | """Turn an IPv6 ip_str into an integer. | |||
|
1403 | ||||
|
1404 | Args: | |||
|
1405 | ip_str: A string, the IPv6 ip_str. | |||
|
1406 | ||||
|
1407 | Returns: | |||
|
1408 | A long, the IPv6 ip_str. | |||
|
1409 | ||||
|
1410 | Raises: | |||
|
1411 | AddressValueError: if ip_str isn't a valid IPv6 Address. | |||
|
1412 | ||||
|
1413 | """ | |||
|
1414 | parts = ip_str.split(':') | |||
|
1415 | ||||
|
1416 | # An IPv6 address needs at least 2 colons (3 parts). | |||
|
1417 | if len(parts) < 3: | |||
|
1418 | raise AddressValueError(ip_str) | |||
|
1419 | ||||
|
1420 | # If the address has an IPv4-style suffix, convert it to hexadecimal. | |||
|
1421 | if '.' in parts[-1]: | |||
|
1422 | ipv4_int = IPv4Address(parts.pop())._ip | |||
|
1423 | parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) | |||
|
1424 | parts.append('%x' % (ipv4_int & 0xFFFF)) | |||
|
1425 | ||||
|
1426 | # An IPv6 address can't have more than 8 colons (9 parts). | |||
|
1427 | if len(parts) > self._HEXTET_COUNT + 1: | |||
|
1428 | raise AddressValueError(ip_str) | |||
|
1429 | ||||
|
1430 | # Disregarding the endpoints, find '::' with nothing in between. | |||
|
1431 | # This indicates that a run of zeroes has been skipped. | |||
|
1432 | try: | |||
|
1433 | skip_index, = ( | |||
|
1434 | [i for i in xrange(1, len(parts) - 1) if not parts[i]] or | |||
|
1435 | [None]) | |||
|
1436 | except ValueError: | |||
|
1437 | # Can't have more than one '::' | |||
|
1438 | raise AddressValueError(ip_str) | |||
|
1439 | ||||
|
1440 | # parts_hi is the number of parts to copy from above/before the '::' | |||
|
1441 | # parts_lo is the number of parts to copy from below/after the '::' | |||
|
1442 | if skip_index is not None: | |||
|
1443 | # If we found a '::', then check if it also covers the endpoints. | |||
|
1444 | parts_hi = skip_index | |||
|
1445 | parts_lo = len(parts) - skip_index - 1 | |||
|
1446 | if not parts[0]: | |||
|
1447 | parts_hi -= 1 | |||
|
1448 | if parts_hi: | |||
|
1449 | raise AddressValueError(ip_str) # ^: requires ^:: | |||
|
1450 | if not parts[-1]: | |||
|
1451 | parts_lo -= 1 | |||
|
1452 | if parts_lo: | |||
|
1453 | raise AddressValueError(ip_str) # :$ requires ::$ | |||
|
1454 | parts_skipped = self._HEXTET_COUNT - (parts_hi + parts_lo) | |||
|
1455 | if parts_skipped < 1: | |||
|
1456 | raise AddressValueError(ip_str) | |||
|
1457 | else: | |||
|
1458 | # Otherwise, allocate the entire address to parts_hi. The endpoints | |||
|
1459 | # could still be empty, but _parse_hextet() will check for that. | |||
|
1460 | if len(parts) != self._HEXTET_COUNT: | |||
|
1461 | raise AddressValueError(ip_str) | |||
|
1462 | parts_hi = len(parts) | |||
|
1463 | parts_lo = 0 | |||
|
1464 | parts_skipped = 0 | |||
|
1465 | ||||
|
1466 | try: | |||
|
1467 | # Now, parse the hextets into a 128-bit integer. | |||
|
1468 | ip_int = 0L | |||
|
1469 | for i in xrange(parts_hi): | |||
|
1470 | ip_int <<= 16 | |||
|
1471 | ip_int |= self._parse_hextet(parts[i]) | |||
|
1472 | ip_int <<= 16 * parts_skipped | |||
|
1473 | for i in xrange(-parts_lo, 0): | |||
|
1474 | ip_int <<= 16 | |||
|
1475 | ip_int |= self._parse_hextet(parts[i]) | |||
|
1476 | return ip_int | |||
|
1477 | except ValueError: | |||
|
1478 | raise AddressValueError(ip_str) | |||
|
1479 | ||||
|
1480 | def _parse_hextet(self, hextet_str): | |||
|
1481 | """Convert an IPv6 hextet string into an integer. | |||
|
1482 | ||||
|
1483 | Args: | |||
|
1484 | hextet_str: A string, the number to parse. | |||
|
1485 | ||||
|
1486 | Returns: | |||
|
1487 | The hextet as an integer. | |||
|
1488 | ||||
|
1489 | Raises: | |||
|
1490 | ValueError: if the input isn't strictly a hex number from [0..FFFF]. | |||
|
1491 | ||||
|
1492 | """ | |||
|
1493 | # Whitelist the characters, since int() allows a lot of bizarre stuff. | |||
|
1494 | if not self._HEX_DIGITS.issuperset(hextet_str): | |||
|
1495 | raise ValueError | |||
|
1496 | if len(hextet_str) > 4: | |||
|
1497 | raise ValueError | |||
|
1498 | hextet_int = int(hextet_str, 16) | |||
|
1499 | if hextet_int > 0xFFFF: | |||
|
1500 | raise ValueError | |||
|
1501 | return hextet_int | |||
|
1502 | ||||
|
1503 | def _compress_hextets(self, hextets): | |||
|
1504 | """Compresses a list of hextets. | |||
|
1505 | ||||
|
1506 | Compresses a list of strings, replacing the longest continuous | |||
|
1507 | sequence of "0" in the list with "" and adding empty strings at | |||
|
1508 | the beginning or at the end of the string such that subsequently | |||
|
1509 | calling ":".join(hextets) will produce the compressed version of | |||
|
1510 | the IPv6 address. | |||
|
1511 | ||||
|
1512 | Args: | |||
|
1513 | hextets: A list of strings, the hextets to compress. | |||
|
1514 | ||||
|
1515 | Returns: | |||
|
1516 | A list of strings. | |||
|
1517 | ||||
|
1518 | """ | |||
|
1519 | best_doublecolon_start = -1 | |||
|
1520 | best_doublecolon_len = 0 | |||
|
1521 | doublecolon_start = -1 | |||
|
1522 | doublecolon_len = 0 | |||
|
1523 | for index in range(len(hextets)): | |||
|
1524 | if hextets[index] == '0': | |||
|
1525 | doublecolon_len += 1 | |||
|
1526 | if doublecolon_start == -1: | |||
|
1527 | # Start of a sequence of zeros. | |||
|
1528 | doublecolon_start = index | |||
|
1529 | if doublecolon_len > best_doublecolon_len: | |||
|
1530 | # This is the longest sequence of zeros so far. | |||
|
1531 | best_doublecolon_len = doublecolon_len | |||
|
1532 | best_doublecolon_start = doublecolon_start | |||
|
1533 | else: | |||
|
1534 | doublecolon_len = 0 | |||
|
1535 | doublecolon_start = -1 | |||
|
1536 | ||||
|
1537 | if best_doublecolon_len > 1: | |||
|
1538 | best_doublecolon_end = (best_doublecolon_start + | |||
|
1539 | best_doublecolon_len) | |||
|
1540 | # For zeros at the end of the address. | |||
|
1541 | if best_doublecolon_end == len(hextets): | |||
|
1542 | hextets += [''] | |||
|
1543 | hextets[best_doublecolon_start:best_doublecolon_end] = [''] | |||
|
1544 | # For zeros at the beginning of the address. | |||
|
1545 | if best_doublecolon_start == 0: | |||
|
1546 | hextets = [''] + hextets | |||
|
1547 | ||||
|
1548 | return hextets | |||
|
1549 | ||||
|
1550 | def _string_from_ip_int(self, ip_int=None): | |||
|
1551 | """Turns a 128-bit integer into hexadecimal notation. | |||
|
1552 | ||||
|
1553 | Args: | |||
|
1554 | ip_int: An integer, the IP address. | |||
|
1555 | ||||
|
1556 | Returns: | |||
|
1557 | A string, the hexadecimal representation of the address. | |||
|
1558 | ||||
|
1559 | Raises: | |||
|
1560 | ValueError: The address is bigger than 128 bits of all ones. | |||
|
1561 | ||||
|
1562 | """ | |||
|
1563 | if not ip_int and ip_int != 0: | |||
|
1564 | ip_int = int(self._ip) | |||
|
1565 | ||||
|
1566 | if ip_int > self._ALL_ONES: | |||
|
1567 | raise ValueError('IPv6 address is too large') | |||
|
1568 | ||||
|
1569 | hex_str = '%032x' % ip_int | |||
|
1570 | hextets = [] | |||
|
1571 | for x in range(0, 32, 4): | |||
|
1572 | hextets.append('%x' % int(hex_str[x:x + 4], 16)) | |||
|
1573 | ||||
|
1574 | hextets = self._compress_hextets(hextets) | |||
|
1575 | return ':'.join(hextets) | |||
|
1576 | ||||
|
1577 | def _explode_shorthand_ip_string(self): | |||
|
1578 | """Expand a shortened IPv6 address. | |||
|
1579 | ||||
|
1580 | Args: | |||
|
1581 | ip_str: A string, the IPv6 address. | |||
|
1582 | ||||
|
1583 | Returns: | |||
|
1584 | A string, the expanded IPv6 address. | |||
|
1585 | ||||
|
1586 | """ | |||
|
1587 | if isinstance(self, _BaseNet): | |||
|
1588 | ip_str = str(self.ip) | |||
|
1589 | else: | |||
|
1590 | ip_str = str(self) | |||
|
1591 | ||||
|
1592 | ip_int = self._ip_int_from_string(ip_str) | |||
|
1593 | parts = [] | |||
|
1594 | for i in xrange(self._HEXTET_COUNT): | |||
|
1595 | parts.append('%04x' % (ip_int & 0xFFFF)) | |||
|
1596 | ip_int >>= 16 | |||
|
1597 | parts.reverse() | |||
|
1598 | if isinstance(self, _BaseNet): | |||
|
1599 | return '%s/%d' % (':'.join(parts), self.prefixlen) | |||
|
1600 | return ':'.join(parts) | |||
|
1601 | ||||
|
1602 | @property | |||
|
1603 | def max_prefixlen(self): | |||
|
1604 | return self._max_prefixlen | |||
|
1605 | ||||
|
1606 | @property | |||
|
1607 | def packed(self): | |||
|
1608 | """The binary representation of this address.""" | |||
|
1609 | return v6_int_to_packed(self._ip) | |||
|
1610 | ||||
|
1611 | @property | |||
|
1612 | def version(self): | |||
|
1613 | return self._version | |||
|
1614 | ||||
|
1615 | @property | |||
|
1616 | def is_multicast(self): | |||
|
1617 | """Test if the address is reserved for multicast use. | |||
|
1618 | ||||
|
1619 | Returns: | |||
|
1620 | A boolean, True if the address is a multicast address. | |||
|
1621 | See RFC 2373 2.7 for details. | |||
|
1622 | ||||
|
1623 | """ | |||
|
1624 | return self in IPv6Network('ff00::/8') | |||
|
1625 | ||||
|
1626 | @property | |||
|
1627 | def is_reserved(self): | |||
|
1628 | """Test if the address is otherwise IETF reserved. | |||
|
1629 | ||||
|
1630 | Returns: | |||
|
1631 | A boolean, True if the address is within one of the | |||
|
1632 | reserved IPv6 Network ranges. | |||
|
1633 | ||||
|
1634 | """ | |||
|
1635 | return (self in IPv6Network('::/8') or | |||
|
1636 | self in IPv6Network('100::/8') or | |||
|
1637 | self in IPv6Network('200::/7') or | |||
|
1638 | self in IPv6Network('400::/6') or | |||
|
1639 | self in IPv6Network('800::/5') or | |||
|
1640 | self in IPv6Network('1000::/4') or | |||
|
1641 | self in IPv6Network('4000::/3') or | |||
|
1642 | self in IPv6Network('6000::/3') or | |||
|
1643 | self in IPv6Network('8000::/3') or | |||
|
1644 | self in IPv6Network('A000::/3') or | |||
|
1645 | self in IPv6Network('C000::/3') or | |||
|
1646 | self in IPv6Network('E000::/4') or | |||
|
1647 | self in IPv6Network('F000::/5') or | |||
|
1648 | self in IPv6Network('F800::/6') or | |||
|
1649 | self in IPv6Network('FE00::/9')) | |||
|
1650 | ||||
|
1651 | @property | |||
|
1652 | def is_unspecified(self): | |||
|
1653 | """Test if the address is unspecified. | |||
|
1654 | ||||
|
1655 | Returns: | |||
|
1656 | A boolean, True if this is the unspecified address as defined in | |||
|
1657 | RFC 2373 2.5.2. | |||
|
1658 | ||||
|
1659 | """ | |||
|
1660 | return self._ip == 0 and getattr(self, '_prefixlen', 128) == 128 | |||
|
1661 | ||||
|
1662 | @property | |||
|
1663 | def is_loopback(self): | |||
|
1664 | """Test if the address is a loopback address. | |||
|
1665 | ||||
|
1666 | Returns: | |||
|
1667 | A boolean, True if the address is a loopback address as defined in | |||
|
1668 | RFC 2373 2.5.3. | |||
|
1669 | ||||
|
1670 | """ | |||
|
1671 | return self._ip == 1 and getattr(self, '_prefixlen', 128) == 128 | |||
|
1672 | ||||
|
1673 | @property | |||
|
1674 | def is_link_local(self): | |||
|
1675 | """Test if the address is reserved for link-local. | |||
|
1676 | ||||
|
1677 | Returns: | |||
|
1678 | A boolean, True if the address is reserved per RFC 4291. | |||
|
1679 | ||||
|
1680 | """ | |||
|
1681 | return self in IPv6Network('fe80::/10') | |||
|
1682 | ||||
|
1683 | @property | |||
|
1684 | def is_site_local(self): | |||
|
1685 | """Test if the address is reserved for site-local. | |||
|
1686 | ||||
|
1687 | Note that the site-local address space has been deprecated by RFC 3879. | |||
|
1688 | Use is_private to test if this address is in the space of unique local | |||
|
1689 | addresses as defined by RFC 4193. | |||
|
1690 | ||||
|
1691 | Returns: | |||
|
1692 | A boolean, True if the address is reserved per RFC 3513 2.5.6. | |||
|
1693 | ||||
|
1694 | """ | |||
|
1695 | return self in IPv6Network('fec0::/10') | |||
|
1696 | ||||
|
1697 | @property | |||
|
1698 | def is_private(self): | |||
|
1699 | """Test if this address is allocated for private networks. | |||
|
1700 | ||||
|
1701 | Returns: | |||
|
1702 | A boolean, True if the address is reserved per RFC 4193. | |||
|
1703 | ||||
|
1704 | """ | |||
|
1705 | return self in IPv6Network('fc00::/7') | |||
|
1706 | ||||
|
1707 | @property | |||
|
1708 | def ipv4_mapped(self): | |||
|
1709 | """Return the IPv4 mapped address. | |||
|
1710 | ||||
|
1711 | Returns: | |||
|
1712 | If the IPv6 address is a v4 mapped address, return the | |||
|
1713 | IPv4 mapped address. Return None otherwise. | |||
|
1714 | ||||
|
1715 | """ | |||
|
1716 | if (self._ip >> 32) != 0xFFFF: | |||
|
1717 | return None | |||
|
1718 | return IPv4Address(self._ip & 0xFFFFFFFF) | |||
|
1719 | ||||
|
1720 | @property | |||
|
1721 | def teredo(self): | |||
|
1722 | """Tuple of embedded teredo IPs. | |||
|
1723 | ||||
|
1724 | Returns: | |||
|
1725 | Tuple of the (server, client) IPs or None if the address | |||
|
1726 | doesn't appear to be a teredo address (doesn't start with | |||
|
1727 | 2001::/32) | |||
|
1728 | ||||
|
1729 | """ | |||
|
1730 | if (self._ip >> 96) != 0x20010000: | |||
|
1731 | return None | |||
|
1732 | return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), | |||
|
1733 | IPv4Address(~self._ip & 0xFFFFFFFF)) | |||
|
1734 | ||||
|
1735 | @property | |||
|
1736 | def sixtofour(self): | |||
|
1737 | """Return the IPv4 6to4 embedded address. | |||
|
1738 | ||||
|
1739 | Returns: | |||
|
1740 | The IPv4 6to4-embedded address if present or None if the | |||
|
1741 | address doesn't appear to contain a 6to4 embedded address. | |||
|
1742 | ||||
|
1743 | """ | |||
|
1744 | if (self._ip >> 112) != 0x2002: | |||
|
1745 | return None | |||
|
1746 | return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) | |||
|
1747 | ||||
|
1748 | ||||
|
1749 | class IPv6Address(_BaseV6, _BaseIP): | |||
|
1750 | ||||
|
1751 | """Represent and manipulate single IPv6 Addresses. | |||
|
1752 | """ | |||
|
1753 | ||||
|
1754 | def __init__(self, address): | |||
|
1755 | """Instantiate a new IPv6 address object. | |||
|
1756 | ||||
|
1757 | Args: | |||
|
1758 | address: A string or integer representing the IP | |||
|
1759 | ||||
|
1760 | Additionally, an integer can be passed, so | |||
|
1761 | IPv6Address('2001:4860::') == | |||
|
1762 | IPv6Address(42541956101370907050197289607612071936L). | |||
|
1763 | or, more generally | |||
|
1764 | IPv6Address(IPv6Address('2001:4860::')._ip) == | |||
|
1765 | IPv6Address('2001:4860::') | |||
|
1766 | ||||
|
1767 | Raises: | |||
|
1768 | AddressValueError: If address isn't a valid IPv6 address. | |||
|
1769 | ||||
|
1770 | """ | |||
|
1771 | _BaseV6.__init__(self, address) | |||
|
1772 | ||||
|
1773 | # Efficient constructor from integer. | |||
|
1774 | if isinstance(address, (int, long)): | |||
|
1775 | self._ip = address | |||
|
1776 | if address < 0 or address > self._ALL_ONES: | |||
|
1777 | raise AddressValueError(address) | |||
|
1778 | return | |||
|
1779 | ||||
|
1780 | # Constructing from a packed address | |||
|
1781 | if isinstance(address, Bytes): | |||
|
1782 | try: | |||
|
1783 | hi, lo = struct.unpack('!QQ', address) | |||
|
1784 | except struct.error: | |||
|
1785 | raise AddressValueError(address) # Wrong length. | |||
|
1786 | self._ip = (hi << 64) | lo | |||
|
1787 | return | |||
|
1788 | ||||
|
1789 | # Assume input argument to be string or any object representation | |||
|
1790 | # which converts into a formatted IP string. | |||
|
1791 | addr_str = str(address) | |||
|
1792 | if not addr_str: | |||
|
1793 | raise AddressValueError('') | |||
|
1794 | ||||
|
1795 | self._ip = self._ip_int_from_string(addr_str) | |||
|
1796 | ||||
|
1797 | ||||
|
1798 | class IPv6Network(_BaseV6, _BaseNet): | |||
|
1799 | ||||
|
1800 | """This class represents and manipulates 128-bit IPv6 networks. | |||
|
1801 | ||||
|
1802 | Attributes: [examples for IPv6('2001:658:22A:CAFE:200::1/64')] | |||
|
1803 | .ip: IPv6Address('2001:658:22a:cafe:200::1') | |||
|
1804 | .network: IPv6Address('2001:658:22a:cafe::') | |||
|
1805 | .hostmask: IPv6Address('::ffff:ffff:ffff:ffff') | |||
|
1806 | .broadcast: IPv6Address('2001:658:22a:cafe:ffff:ffff:ffff:ffff') | |||
|
1807 | .netmask: IPv6Address('ffff:ffff:ffff:ffff::') | |||
|
1808 | .prefixlen: 64 | |||
|
1809 | ||||
|
1810 | """ | |||
|
1811 | ||||
|
1812 | def __init__(self, address, strict=False): | |||
|
1813 | """Instantiate a new IPv6 Network object. | |||
|
1814 | ||||
|
1815 | Args: | |||
|
1816 | address: A string or integer representing the IPv6 network or the IP | |||
|
1817 | and prefix/netmask. | |||
|
1818 | '2001:4860::/128' | |||
|
1819 | '2001:4860:0000:0000:0000:0000:0000:0000/128' | |||
|
1820 | '2001:4860::' | |||
|
1821 | are all functionally the same in IPv6. That is to say, | |||
|
1822 | failing to provide a subnetmask will create an object with | |||
|
1823 | a mask of /128. | |||
|
1824 | ||||
|
1825 | Additionally, an integer can be passed, so | |||
|
1826 | IPv6Network('2001:4860::') == | |||
|
1827 | IPv6Network(42541956101370907050197289607612071936L). | |||
|
1828 | or, more generally | |||
|
1829 | IPv6Network(IPv6Network('2001:4860::')._ip) == | |||
|
1830 | IPv6Network('2001:4860::') | |||
|
1831 | ||||
|
1832 | strict: A boolean. If true, ensure that we have been passed | |||
|
1833 | A true network address, eg, 192.168.1.0/24 and not an | |||
|
1834 | IP address on a network, eg, 192.168.1.1/24. | |||
|
1835 | ||||
|
1836 | Raises: | |||
|
1837 | AddressValueError: If address isn't a valid IPv6 address. | |||
|
1838 | NetmaskValueError: If the netmask isn't valid for | |||
|
1839 | an IPv6 address. | |||
|
1840 | ValueError: If strict was True and a network address was not | |||
|
1841 | supplied. | |||
|
1842 | ||||
|
1843 | """ | |||
|
1844 | _BaseNet.__init__(self, address) | |||
|
1845 | _BaseV6.__init__(self, address) | |||
|
1846 | ||||
|
1847 | # Constructing from an integer or packed bytes. | |||
|
1848 | if isinstance(address, (int, long, Bytes)): | |||
|
1849 | self.ip = IPv6Address(address) | |||
|
1850 | self._ip = self.ip._ip | |||
|
1851 | self._prefixlen = self._max_prefixlen | |||
|
1852 | self.netmask = IPv6Address(self._ALL_ONES) | |||
|
1853 | return | |||
|
1854 | ||||
|
1855 | # Assume input argument to be string or any object representation | |||
|
1856 | # which converts into a formatted IP prefix string. | |||
|
1857 | addr = str(address).split('/') | |||
|
1858 | ||||
|
1859 | if len(addr) > 2: | |||
|
1860 | raise AddressValueError(address) | |||
|
1861 | ||||
|
1862 | self._ip = self._ip_int_from_string(addr[0]) | |||
|
1863 | self.ip = IPv6Address(self._ip) | |||
|
1864 | ||||
|
1865 | if len(addr) == 2: | |||
|
1866 | if self._is_valid_netmask(addr[1]): | |||
|
1867 | self._prefixlen = int(addr[1]) | |||
|
1868 | else: | |||
|
1869 | raise NetmaskValueError(addr[1]) | |||
|
1870 | else: | |||
|
1871 | self._prefixlen = self._max_prefixlen | |||
|
1872 | ||||
|
1873 | self.netmask = IPv6Address(self._ip_int_from_prefix(self._prefixlen)) | |||
|
1874 | ||||
|
1875 | if strict: | |||
|
1876 | if self.ip != self.network: | |||
|
1877 | raise ValueError('%s has host bits set' % | |||
|
1878 | self.ip) | |||
|
1879 | if self._prefixlen == (self._max_prefixlen - 1): | |||
|
1880 | self.iterhosts = self.__iter__ | |||
|
1881 | ||||
|
1882 | def _is_valid_netmask(self, prefixlen): | |||
|
1883 | """Verify that the netmask/prefixlen is valid. | |||
|
1884 | ||||
|
1885 | Args: | |||
|
1886 | prefixlen: A string, the netmask in prefix length format. | |||
|
1887 | ||||
|
1888 | Returns: | |||
|
1889 | A boolean, True if the prefix represents a valid IPv6 | |||
|
1890 | netmask. | |||
|
1891 | ||||
|
1892 | """ | |||
|
1893 | try: | |||
|
1894 | prefixlen = int(prefixlen) | |||
|
1895 | except ValueError: | |||
|
1896 | return False | |||
|
1897 | return 0 <= prefixlen <= self._max_prefixlen | |||
|
1898 | ||||
|
1899 | @property | |||
|
1900 | def with_netmask(self): | |||
|
1901 | return self.with_prefixlen |
@@ -32,3 +32,4 b' List of contributors to RhodeCode projec' | |||||
32 | Raoul Thill <raoul.thill@gmail.com> |
|
32 | Raoul Thill <raoul.thill@gmail.com> | |
33 | Philip Jameson <philip.j@hostdime.com> |
|
33 | Philip Jameson <philip.j@hostdime.com> | |
34 | Mads Kiilerich <madski@unity3d.com> |
|
34 | Mads Kiilerich <madski@unity3d.com> | |
|
35 | Dan Sheridan <djs@adelard.com> |
@@ -155,9 +155,10 b' OUTPUT::' | |||||
155 | lock |
|
155 | lock | |
156 | ---- |
|
156 | ---- | |
157 |
|
157 | |||
158 | Set locking state on given repository by given user. |
|
158 | Set locking state on given repository by given user. If userid param is skipped | |
|
159 | , then it is set to id of user whos calling this method. | |||
159 | This command can be executed only using api_key belonging to user with admin |
|
160 | This command can be executed only using api_key belonging to user with admin | |
160 | rights. |
|
161 | rights or regular user that have admin or write access to repository. | |
161 |
|
162 | |||
162 | INPUT:: |
|
163 | INPUT:: | |
163 |
|
164 | |||
@@ -166,9 +167,8 b' INPUT::' | |||||
166 | method : "lock" |
|
167 | method : "lock" | |
167 | args : { |
|
168 | args : { | |
168 | "repoid" : "<reponame or repo_id>" |
|
169 | "repoid" : "<reponame or repo_id>" | |
169 | "userid" : "<user_id or username>", |
|
170 | "userid" : "<user_id or username = Optional(=apiuser)>", | |
170 | "locked" : "<bool true|false>" |
|
171 | "locked" : "<bool true|false>" | |
171 |
|
||||
172 | } |
|
172 | } | |
173 |
|
173 | |||
174 | OUTPUT:: |
|
174 | OUTPUT:: | |
@@ -178,12 +178,47 b' OUTPUT::' | |||||
178 | error : null |
|
178 | error : null | |
179 |
|
179 | |||
180 |
|
180 | |||
|
181 | show_ip | |||
|
182 | ------- | |||
|
183 | ||||
|
184 | Shows IP address as seen from RhodeCode server, together with all | |||
|
185 | defined IP addresses for given user. | |||
|
186 | This command can be executed only using api_key belonging to user with admin | |||
|
187 | rights. | |||
|
188 | ||||
|
189 | INPUT:: | |||
|
190 | ||||
|
191 | id : <id_for_response> | |||
|
192 | api_key : "<api_key>" | |||
|
193 | method : "show_ip" | |||
|
194 | args : { | |||
|
195 | "userid" : "<user_id or username>", | |||
|
196 | } | |||
|
197 | ||||
|
198 | OUTPUT:: | |||
|
199 | ||||
|
200 | id : <id_given_in_input> | |||
|
201 | result : { | |||
|
202 | "ip_addr_server": <ip_from_clien>", | |||
|
203 | "user_ips": [ | |||
|
204 | { | |||
|
205 | "ip_addr": "<ip_with_mask>", | |||
|
206 | "ip_range": ["<start_ip>", "<end_ip>"], | |||
|
207 | }, | |||
|
208 | ... | |||
|
209 | ] | |||
|
210 | } | |||
|
211 | ||||
|
212 | error : null | |||
|
213 | ||||
|
214 | ||||
181 | get_user |
|
215 | get_user | |
182 | -------- |
|
216 | -------- | |
183 |
|
217 | |||
184 | Get's an user by username or user_id, Returns empty result if user is not found. |
|
218 | Get's an user by username or user_id, Returns empty result if user is not found. | |
|
219 | If userid param is skipped it is set to id of user who is calling this method. | |||
185 | This command can be executed only using api_key belonging to user with admin |
|
220 | This command can be executed only using api_key belonging to user with admin | |
186 | rights. |
|
221 | rights, or regular users that cannot specify different userid than theirs | |
187 |
|
222 | |||
188 |
|
223 | |||
189 | INPUT:: |
|
224 | INPUT:: | |
@@ -192,7 +227,7 b' INPUT::' | |||||
192 | api_key : "<api_key>" |
|
227 | api_key : "<api_key>" | |
193 | method : "get_user" |
|
228 | method : "get_user" | |
194 | args : { |
|
229 | args : { | |
195 | "userid" : "<username or user_id>" |
|
230 | "userid" : "<username or user_id Optional(=apiuser)>" | |
196 | } |
|
231 | } | |
197 |
|
232 | |||
198 | OUTPUT:: |
|
233 | OUTPUT:: | |
@@ -200,16 +235,17 b' OUTPUT::' | |||||
200 | id : <id_given_in_input> |
|
235 | id : <id_given_in_input> | |
201 | result: None if user does not exist or |
|
236 | result: None if user does not exist or | |
202 | { |
|
237 | { | |
203 | "user_id" : "<user_id>", |
|
238 | "user_id" : "<user_id>", | |
204 | "username" : "<username>", |
|
239 | "username" : "<username>", | |
205 | "firstname": "<firstname>", |
|
240 | "firstname": "<firstname>", | |
206 | "lastname" : "<lastname>", |
|
241 | "lastname" : "<lastname>", | |
207 | "email" : "<email>", |
|
242 | "email" : "<email>", | |
208 | "emails": "<list_of_all_additional_emails>", |
|
243 | "emails": "<list_of_all_additional_emails>", | |
209 | "active" : "<bool>", |
|
244 | "ip_addresses": "<list_of_ip_addresses_for_user>", | |
210 |
"a |
|
245 | "active" : "<bool>", | |
211 |
" |
|
246 | "admin" : "<bool>", | |
212 |
"l |
|
247 | "ldap_dn" : "<ldap_dn>", | |
|
248 | "last_login": "<last_login>", | |||
213 | "permissions": { |
|
249 | "permissions": { | |
214 | "global": ["hg.create.repository", |
|
250 | "global": ["hg.create.repository", | |
215 | "repository.read", |
|
251 | "repository.read", | |
@@ -241,16 +277,17 b' OUTPUT::' | |||||
241 | id : <id_given_in_input> |
|
277 | id : <id_given_in_input> | |
242 | result: [ |
|
278 | result: [ | |
243 | { |
|
279 | { | |
244 | "user_id" : "<user_id>", |
|
280 | "user_id" : "<user_id>", | |
245 | "username" : "<username>", |
|
281 | "username" : "<username>", | |
246 | "firstname": "<firstname>", |
|
282 | "firstname": "<firstname>", | |
247 | "lastname" : "<lastname>", |
|
283 | "lastname" : "<lastname>", | |
248 | "email" : "<email>", |
|
284 | "email" : "<email>", | |
249 | "emails": "<list_of_all_additional_emails>", |
|
285 | "emails": "<list_of_all_additional_emails>", | |
250 | "active" : "<bool>", |
|
286 | "ip_addresses": "<list_of_ip_addresses_for_user>", | |
251 |
"a |
|
287 | "active" : "<bool>", | |
252 |
" |
|
288 | "admin" : "<bool>", | |
253 |
"l |
|
289 | "ldap_dn" : "<ldap_dn>", | |
|
290 | "last_login": "<last_login>", | |||
254 | }, |
|
291 | }, | |
255 | … |
|
292 | … | |
256 | ] |
|
293 | ] | |
@@ -315,14 +352,14 b' INPUT::' | |||||
315 | method : "update_user" |
|
352 | method : "update_user" | |
316 | args : { |
|
353 | args : { | |
317 | "userid" : "<user_id or username>", |
|
354 | "userid" : "<user_id or username>", | |
318 | "username" : "<username> = Optional", |
|
355 | "username" : "<username> = Optional(None)", | |
319 | "email" : "<useremail> = Optional", |
|
356 | "email" : "<useremail> = Optional(None)", | |
320 | "password" : "<password> = Optional", |
|
357 | "password" : "<password> = Optional(None)", | |
321 | "firstname" : "<firstname> = Optional", |
|
358 | "firstname" : "<firstname> = Optional(None)", | |
322 | "lastname" : "<lastname> = Optional", |
|
359 | "lastname" : "<lastname> = Optional(None)", | |
323 | "active" : "<bool> = Optional", |
|
360 | "active" : "<bool> = Optional(None)", | |
324 | "admin" : "<bool> = Optional", |
|
361 | "admin" : "<bool> = Optional(None)", | |
325 | "ldap_dn" : "<ldap_dn> = Optional" |
|
362 | "ldap_dn" : "<ldap_dn> = Optional(None)" | |
326 | } |
|
363 | } | |
327 |
|
364 | |||
328 | OUTPUT:: |
|
365 | OUTPUT:: | |
@@ -537,8 +574,9 b' get_repo' | |||||
537 | -------- |
|
574 | -------- | |
538 |
|
575 | |||
539 | Gets an existing repository by it's name or repository_id. Members will return |
|
576 | Gets an existing repository by it's name or repository_id. Members will return | |
540 | either users_group or user associated to that repository. This command can |
|
577 | either users_group or user associated to that repository. This command can be | |
541 |
|
|
578 | executed only using api_key belonging to user with admin | |
|
579 | rights or regular user that have at least read access to repository. | |||
542 |
|
580 | |||
543 |
|
581 | |||
544 | INPUT:: |
|
582 | INPUT:: | |
@@ -555,29 +593,40 b' OUTPUT::' | |||||
555 | id : <id_given_in_input> |
|
593 | id : <id_given_in_input> | |
556 | result: None if repository does not exist or |
|
594 | result: None if repository does not exist or | |
557 | { |
|
595 | { | |
558 | "repo_id" : "<repo_id>", |
|
596 | "repo_id" : "<repo_id>", | |
559 | "repo_name" : "<reponame>" |
|
597 | "repo_name" : "<reponame>" | |
560 | "repo_type" : "<repo_type>", |
|
598 | "repo_type" : "<repo_type>", | |
561 | "clone_uri" : "<clone_uri>", |
|
599 | "clone_uri" : "<clone_uri>", | |
562 |
" |
|
600 | "enable_downloads": "<bool>", | |
563 | "created_on" : "<datetimecreated>", |
|
601 | "enable_locking": "<bool>", | |
564 | "description" : "<description>", |
|
602 | "enable_statistics": "<bool>", | |
565 |
" |
|
603 | "private": "<bool>", | |
566 | "owner": "<repo_owner>", |
|
604 | "created_on" : "<date_time_created>", | |
567 | "fork_of": "<name_of_fork_parent>", |
|
605 | "description" : "<description>", | |
|
606 | "landing_rev": "<landing_rev>", | |||
|
607 | "last_changeset": { | |||
|
608 | "author": "<full_author>", | |||
|
609 | "date": "<date_time_of_commit>", | |||
|
610 | "message": "<commit_message>", | |||
|
611 | "raw_id": "<raw_id>", | |||
|
612 | "revision": "<numeric_revision>", | |||
|
613 | "short_id": "<short_id>" | |||
|
614 | } | |||
|
615 | "owner": "<repo_owner>", | |||
|
616 | "fork_of": "<name_of_fork_parent>", | |||
568 | "members" : [ |
|
617 | "members" : [ | |
569 | { |
|
618 | { | |
570 | "type": "user", |
|
619 | "type": "user", | |
571 | "user_id" : "<user_id>", |
|
620 | "user_id" : "<user_id>", | |
572 | "username" : "<username>", |
|
621 | "username" : "<username>", | |
573 | "firstname": "<firstname>", |
|
622 | "firstname": "<firstname>", | |
574 | "lastname" : "<lastname>", |
|
623 | "lastname" : "<lastname>", | |
575 | "email" : "<email>", |
|
624 | "email" : "<email>", | |
576 | "emails": "<list_of_all_additional_emails>", |
|
625 | "emails": "<list_of_all_additional_emails>", | |
577 | "active" : "<bool>", |
|
626 | "active" : "<bool>", | |
578 | "admin" : "<bool>", |
|
627 | "admin" : "<bool>", | |
579 | "ldap_dn" : "<ldap_dn>", |
|
628 | "ldap_dn" : "<ldap_dn>", | |
580 | "last_login": "<last_login>", |
|
629 | "last_login": "<last_login>", | |
581 | "permission" : "repository.(read|write|admin)" |
|
630 | "permission" : "repository.(read|write|admin)" | |
582 | }, |
|
631 | }, | |
583 | … |
|
632 | … | |
@@ -597,8 +646,9 b' OUTPUT::' | |||||
597 | get_repos |
|
646 | get_repos | |
598 | --------- |
|
647 | --------- | |
599 |
|
648 | |||
600 |
Lists all existing repositories. This command can be executed only using |
|
649 | Lists all existing repositories. This command can be executed only using | |
601 | belonging to user with admin rights |
|
650 | api_key belonging to user with admin rights or regular user that have | |
|
651 | admin, write or read access to repository. | |||
602 |
|
652 | |||
603 |
|
653 | |||
604 | INPUT:: |
|
654 | INPUT:: | |
@@ -613,16 +663,19 b' OUTPUT::' | |||||
613 | id : <id_given_in_input> |
|
663 | id : <id_given_in_input> | |
614 | result: [ |
|
664 | result: [ | |
615 | { |
|
665 | { | |
616 | "repo_id" : "<repo_id>", |
|
666 | "repo_id" : "<repo_id>", | |
617 | "repo_name" : "<reponame>" |
|
667 | "repo_name" : "<reponame>" | |
618 | "repo_type" : "<repo_type>", |
|
668 | "repo_type" : "<repo_type>", | |
619 | "clone_uri" : "<clone_uri>", |
|
669 | "clone_uri" : "<clone_uri>", | |
620 | "private": : "<bool>", |
|
670 | "private": : "<bool>", | |
621 | "created_on" : "<datetimecreated>", |
|
671 | "created_on" : "<datetimecreated>", | |
622 | "description" : "<description>", |
|
672 | "description" : "<description>", | |
623 | "landing_rev": "<landing_rev>", |
|
673 | "landing_rev": "<landing_rev>", | |
624 | "owner": "<repo_owner>", |
|
674 | "owner": "<repo_owner>", | |
625 | "fork_of": "<name_of_fork_parent>", |
|
675 | "fork_of": "<name_of_fork_parent>", | |
|
676 | "enable_downloads": "<bool>", | |||
|
677 | "enable_locking": "<bool>", | |||
|
678 | "enable_statistics": "<bool>", | |||
626 | }, |
|
679 | }, | |
627 | … |
|
680 | … | |
628 | ] |
|
681 | ] | |
@@ -666,11 +719,12 b' OUTPUT::' | |||||
666 | create_repo |
|
719 | create_repo | |
667 | ----------- |
|
720 | ----------- | |
668 |
|
721 | |||
669 | Creates a repository. This command can be executed only using api_key |
|
722 | Creates a repository. If repository name contains "/", all needed repository | |
670 | belonging to user with admin rights. |
|
723 | groups will be created. For example "foo/bar/baz" will create groups | |
671 | If repository name contains "/", all needed repository groups will be created. |
|
724 | "foo", "bar" (with "foo" as parent), and create "baz" repository with | |
672 | For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent), |
|
725 | "bar" as group. This command can be executed only using api_key belonging to user with admin | |
673 | and create "baz" repository with "bar" as group. |
|
726 | rights or regular user that have create repository permission. Regular users | |
|
727 | cannot specify owner parameter | |||
674 |
|
728 | |||
675 |
|
729 | |||
676 | INPUT:: |
|
730 | INPUT:: | |
@@ -679,13 +733,16 b' INPUT::' | |||||
679 | api_key : "<api_key>" |
|
733 | api_key : "<api_key>" | |
680 | method : "create_repo" |
|
734 | method : "create_repo" | |
681 | args: { |
|
735 | args: { | |
682 | "repo_name" : "<reponame>", |
|
736 | "repo_name" : "<reponame>", | |
683 | "owner" : "<onwer_name_or_id>", |
|
737 | "owner" : "<onwer_name_or_id = Optional(=apiuser)>", | |
684 | "repo_type" : "<repo_type>", |
|
738 | "repo_type" : "<repo_type> = Optional('hg')", | |
685 | "description" : "<description> = Optional('')", |
|
739 | "description" : "<description> = Optional('')", | |
686 | "private" : "<bool> = Optional(False)", |
|
740 | "private" : "<bool> = Optional(False)", | |
687 | "clone_uri" : "<clone_uri> = Optional(None)", |
|
741 | "clone_uri" : "<clone_uri> = Optional(None)", | |
688 | "landing_rev" : "<landing_rev> = Optional('tip')", |
|
742 | "landing_rev" : "<landing_rev> = Optional('tip')", | |
|
743 | "enable_downloads": "<bool> = Optional(False)", | |||
|
744 | "enable_locking": "<bool> = Optional(False)", | |||
|
745 | "enable_statistics": "<bool> = Optional(False)", | |||
689 | } |
|
746 | } | |
690 |
|
747 | |||
691 | OUTPUT:: |
|
748 | OUTPUT:: | |
@@ -694,26 +751,65 b' OUTPUT::' | |||||
694 | result: { |
|
751 | result: { | |
695 | "msg": "Created new repository `<reponame>`", |
|
752 | "msg": "Created new repository `<reponame>`", | |
696 | "repo": { |
|
753 | "repo": { | |
697 | "repo_id" : "<repo_id>", |
|
754 | "repo_id" : "<repo_id>", | |
698 | "repo_name" : "<reponame>" |
|
755 | "repo_name" : "<reponame>" | |
699 | "repo_type" : "<repo_type>", |
|
756 | "repo_type" : "<repo_type>", | |
700 | "clone_uri" : "<clone_uri>", |
|
757 | "clone_uri" : "<clone_uri>", | |
701 | "private": : "<bool>", |
|
758 | "private": : "<bool>", | |
702 | "created_on" : "<datetimecreated>", |
|
759 | "created_on" : "<datetimecreated>", | |
703 | "description" : "<description>", |
|
760 | "description" : "<description>", | |
704 | "landing_rev": "<landing_rev>", |
|
761 | "landing_rev": "<landing_rev>", | |
705 |
"owner": |
|
762 | "owner": "<username or user_id>", | |
706 | "fork_of": "<name_of_fork_parent>", |
|
763 | "fork_of": "<name_of_fork_parent>", | |
|
764 | "enable_downloads": "<bool>", | |||
|
765 | "enable_locking": "<bool>", | |||
|
766 | "enable_statistics": "<bool>", | |||
707 | }, |
|
767 | }, | |
708 | } |
|
768 | } | |
709 | error: null |
|
769 | error: null | |
710 |
|
770 | |||
711 |
|
771 | |||
|
772 | fork_repo | |||
|
773 | --------- | |||
|
774 | ||||
|
775 | Creates a fork of given repo. In case of using celery this will | |||
|
776 | immidiatelly return success message, while fork is going to be created | |||
|
777 | asynchronous. This command can be executed only using api_key belonging to | |||
|
778 | user with admin rights or regular user that have fork permission, and at least | |||
|
779 | read access to forking repository. Regular users cannot specify owner parameter. | |||
|
780 | ||||
|
781 | ||||
|
782 | INPUT:: | |||
|
783 | ||||
|
784 | id : <id_for_response> | |||
|
785 | api_key : "<api_key>" | |||
|
786 | method : "fork_repo" | |||
|
787 | args: { | |||
|
788 | "repoid" : "<reponame or repo_id>", | |||
|
789 | "fork_name": "<forkname>", | |||
|
790 | "owner": "<username or user_id = Optional(=apiuser)>", | |||
|
791 | "description": "<description>", | |||
|
792 | "copy_permissions": "<bool>", | |||
|
793 | "private": "<bool>", | |||
|
794 | "landing_rev": "<landing_rev>" | |||
|
795 | ||||
|
796 | } | |||
|
797 | ||||
|
798 | OUTPUT:: | |||
|
799 | ||||
|
800 | id : <id_given_in_input> | |||
|
801 | result: { | |||
|
802 | "msg": "Created fork of `<reponame>` as `<forkname>`", | |||
|
803 | "success": true | |||
|
804 | } | |||
|
805 | error: null | |||
|
806 | ||||
|
807 | ||||
712 | delete_repo |
|
808 | delete_repo | |
713 | ----------- |
|
809 | ----------- | |
714 |
|
810 | |||
715 | Deletes a repository. This command can be executed only using api_key |
|
811 | Deletes a repository. This command can be executed only using api_key belonging to user with admin | |
716 | belonging to user with admin rights. |
|
812 | rights or regular user that have admin access to repository. | |
717 |
|
813 | |||
718 |
|
814 | |||
719 | INPUT:: |
|
815 | INPUT:: |
@@ -4,6 +4,38 b'' | |||||
4 | Changelog |
|
4 | Changelog | |
5 | ========= |
|
5 | ========= | |
6 |
|
6 | |||
|
7 | 1.5.2 (**2013-01-14**) | |||
|
8 | ---------------------- | |||
|
9 | ||||
|
10 | news | |||
|
11 | ++++ | |||
|
12 | ||||
|
13 | - IP restrictions for users. Each user can get a set of whitelist IP+mask for | |||
|
14 | extra protection. Useful for buildbots etc. | |||
|
15 | - added full last changeset info to lightweight dashboard. lightweight dashboard | |||
|
16 | is now fully functional replacement of original dashboard. | |||
|
17 | - implemented certain API calls for non-admin users. | |||
|
18 | - enabled all Markdown Extra plugins | |||
|
19 | - implemented #725 Pull Request View - Show origin repo URL | |||
|
20 | - show comments from pull requests into associated changesets | |||
|
21 | ||||
|
22 | fixes | |||
|
23 | +++++ | |||
|
24 | ||||
|
25 | - update repoinfo script is more failsafe | |||
|
26 | - fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests | |||
|
27 | - fixed #691: Notifications for pull requests: move link to top for better | |||
|
28 | readability | |||
|
29 | - fixed #699: fix missing fork docs for API | |||
|
30 | - fixed #693 Opening changeset from pull request fails | |||
|
31 | - fixed #710 File view stripping empty lines from beginning and end of file | |||
|
32 | - fixed issues with getting repos by path on windows, caused GIT hooks to fail | |||
|
33 | - fixed issues with groups paginator on main dashboard | |||
|
34 | - improved fetch/pull command for git repos, now pulling all refs | |||
|
35 | - fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect | |||
|
36 | when running in a subdir | |||
|
37 | - fixed issue #702 API methods without arguments fail when "args":null | |||
|
38 | - set the status of changesets initially on pull request. Fixes issues #690 and #587 | |||
7 |
|
39 | |||
8 | 1.5.1 (**2012-12-13**) |
|
40 | 1.5.1 (**2012-12-13**) | |
9 | ---------------------- |
|
41 | ---------------------- |
@@ -43,6 +43,10 b' For installing RhodeCode i highly recomm' | |||||
43 | way many required by RhodeCode libraries will remain sandboxed from your main |
|
43 | way many required by RhodeCode libraries will remain sandboxed from your main | |
44 | python and making things less problematic when doing system python updates. |
|
44 | python and making things less problematic when doing system python updates. | |
45 |
|
45 | |||
|
46 | Alternative very detailed installation instructions for Ubuntu Server with | |||
|
47 | celery, indexer and daemon scripts: https://gist.github.com/4546398 | |||
|
48 | ||||
|
49 | ||||
46 | - Assuming you have installed virtualenv_ create a new virtual environment |
|
50 | - Assuming you have installed virtualenv_ create a new virtual environment | |
47 | using virtualenv command:: |
|
51 | using virtualenv command:: | |
48 |
|
52 |
@@ -26,7 +26,7 b'' | |||||
26 | import sys |
|
26 | import sys | |
27 | import platform |
|
27 | import platform | |
28 |
|
28 | |||
29 |
VERSION = (1, 5, |
|
29 | VERSION = (1, 5, 2) | |
30 |
|
30 | |||
31 | try: |
|
31 | try: | |
32 | from rhodecode.lib import get_current_revision |
|
32 | from rhodecode.lib import get_current_revision | |
@@ -38,7 +38,7 b' except ImportError:' | |||||
38 |
|
38 | |||
39 | __version__ = ('.'.join((str(each) for each in VERSION[:3])) + |
|
39 | __version__ = ('.'.join((str(each) for each in VERSION[:3])) + | |
40 | '.'.join(VERSION[3:])) |
|
40 | '.'.join(VERSION[3:])) | |
41 |
__dbversion__ = |
|
41 | __dbversion__ = 10 # defines current db version for migrations | |
42 | __platform__ = platform.system() |
|
42 | __platform__ = platform.system() | |
43 | __license__ = 'GPLv3' |
|
43 | __license__ = 'GPLv3' | |
44 | __py_version__ = sys.version_info |
|
44 | __py_version__ = sys.version_info |
@@ -222,6 +222,10 b' def make_map(config):' | |||||
222 | action="add_email", conditions=dict(method=["PUT"])) |
|
222 | action="add_email", conditions=dict(method=["PUT"])) | |
223 | m.connect("user_emails_delete", "/users_emails/{id}", |
|
223 | m.connect("user_emails_delete", "/users_emails/{id}", | |
224 | action="delete_email", conditions=dict(method=["DELETE"])) |
|
224 | action="delete_email", conditions=dict(method=["DELETE"])) | |
|
225 | m.connect("user_ips", "/users_ips/{id}", | |||
|
226 | action="add_ip", conditions=dict(method=["PUT"])) | |||
|
227 | m.connect("user_ips_delete", "/users_ips/{id}", | |||
|
228 | action="delete_ip", conditions=dict(method=["DELETE"])) | |||
225 |
|
229 | |||
226 | #ADMIN USERS GROUPS REST ROUTES |
|
230 | #ADMIN USERS GROUPS REST ROUTES | |
227 | with rmap.submapper(path_prefix=ADMIN_PREFIX, |
|
231 | with rmap.submapper(path_prefix=ADMIN_PREFIX, | |
@@ -355,8 +359,6 b' def make_map(config):' | |||||
355 | m.connect('api', '/api') |
|
359 | m.connect('api', '/api') | |
356 |
|
360 | |||
357 | #USER JOURNAL |
|
361 | #USER JOURNAL | |
358 | rmap.connect('journal_my_repos', '%s/journal_my_repos' % ADMIN_PREFIX, |
|
|||
359 | controller='journal', action='index_my_repos') |
|
|||
360 | rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, |
|
362 | rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, | |
361 | controller='journal', action='index') |
|
363 | controller='journal', action='index') | |
362 | rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX, |
|
364 | rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX, |
@@ -110,8 +110,8 b' class NotificationsController(BaseContro' | |||||
110 | # url('notification', notification_id=ID) |
|
110 | # url('notification', notification_id=ID) | |
111 | try: |
|
111 | try: | |
112 | no = Notification.get(notification_id) |
|
112 | no = Notification.get(notification_id) | |
113 | owner = lambda: (no.notifications_to_users.user.user_id |
|
113 | owner = all(un.user.user_id == c.rhodecode_user.user_id | |
114 |
|
|
114 | for un in no.notifications_to_users) | |
115 | if h.HasPermissionAny('hg.admin')() or owner: |
|
115 | if h.HasPermissionAny('hg.admin')() or owner: | |
116 | NotificationModel().mark_read(c.rhodecode_user.user_id, no) |
|
116 | NotificationModel().mark_read(c.rhodecode_user.user_id, no) | |
117 | Session().commit() |
|
117 | Session().commit() | |
@@ -132,8 +132,8 b' class NotificationsController(BaseContro' | |||||
132 |
|
132 | |||
133 | try: |
|
133 | try: | |
134 | no = Notification.get(notification_id) |
|
134 | no = Notification.get(notification_id) | |
135 | owner = lambda: (no.notifications_to_users.user.user_id |
|
135 | owner = all(un.user.user_id == c.rhodecode_user.user_id | |
136 |
|
|
136 | for un in no.notifications_to_users) | |
137 | if h.HasPermissionAny('hg.admin')() or owner: |
|
137 | if h.HasPermissionAny('hg.admin')() or owner: | |
138 | NotificationModel().delete(c.rhodecode_user.user_id, no) |
|
138 | NotificationModel().delete(c.rhodecode_user.user_id, no) | |
139 | Session().commit() |
|
139 | Session().commit() | |
@@ -149,8 +149,8 b' class NotificationsController(BaseContro' | |||||
149 | c.user = self.rhodecode_user |
|
149 | c.user = self.rhodecode_user | |
150 | no = Notification.get(notification_id) |
|
150 | no = Notification.get(notification_id) | |
151 |
|
151 | |||
152 | owner = lambda: (no.notifications_to_users.user.user_id |
|
152 | owner = all(un.user.user_id == c.rhodecode_user.user_id | |
153 |
|
|
153 | for un in no.notifications_to_users) | |
154 | if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner): |
|
154 | if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner): | |
155 | unotification = NotificationModel()\ |
|
155 | unotification = NotificationModel()\ | |
156 | .get_user_notification(c.user.user_id, no) |
|
156 | .get_user_notification(c.user.user_id, no) |
@@ -33,11 +33,12 b' from pylons.controllers.util import abor' | |||||
33 | from pylons.i18n.translation import _ |
|
33 | from pylons.i18n.translation import _ | |
34 |
|
34 | |||
35 | from rhodecode.lib import helpers as h |
|
35 | from rhodecode.lib import helpers as h | |
36 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator |
|
36 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\ | |
|
37 | AuthUser | |||
37 | from rhodecode.lib.base import BaseController, render |
|
38 | from rhodecode.lib.base import BaseController, render | |
38 | from rhodecode.model.forms import DefaultPermissionsForm |
|
39 | from rhodecode.model.forms import DefaultPermissionsForm | |
39 | from rhodecode.model.permission import PermissionModel |
|
40 | from rhodecode.model.permission import PermissionModel | |
40 | from rhodecode.model.db import User |
|
41 | from rhodecode.model.db import User, UserIpMap | |
41 | from rhodecode.model.meta import Session |
|
42 | from rhodecode.model.meta import Session | |
42 |
|
43 | |||
43 | log = logging.getLogger(__name__) |
|
44 | log = logging.getLogger(__name__) | |
@@ -105,36 +106,41 b' class PermissionsController(BaseControll' | |||||
105 | # h.form(url('permission', id=ID), |
|
106 | # h.form(url('permission', id=ID), | |
106 | # method='put') |
|
107 | # method='put') | |
107 | # url('permission', id=ID) |
|
108 | # url('permission', id=ID) | |
108 |
|
109 | if id == 'default': | ||
109 | permission_model = PermissionModel() |
|
110 | c.user = default_user = User.get_by_username('default') | |
|
111 | c.perm_user = AuthUser(user_id=default_user.user_id) | |||
|
112 | c.user_ip_map = UserIpMap.query()\ | |||
|
113 | .filter(UserIpMap.user == default_user).all() | |||
|
114 | permission_model = PermissionModel() | |||
110 |
|
115 | |||
111 |
_form = DefaultPermissionsForm( |
|
116 | _form = DefaultPermissionsForm( | |
112 |
|
|
117 | [x[0] for x in self.repo_perms_choices], | |
113 |
|
|
118 | [x[0] for x in self.group_perms_choices], | |
114 |
|
|
119 | [x[0] for x in self.register_choices], | |
115 |
|
|
120 | [x[0] for x in self.create_choices], | |
|
121 | [x[0] for x in self.fork_choices])() | |||
116 |
|
122 | |||
117 | try: |
|
123 | try: | |
118 | form_result = _form.to_python(dict(request.POST)) |
|
124 | form_result = _form.to_python(dict(request.POST)) | |
119 | form_result.update({'perm_user_name': id}) |
|
125 | form_result.update({'perm_user_name': id}) | |
120 | permission_model.update(form_result) |
|
126 | permission_model.update(form_result) | |
121 | Session().commit() |
|
127 | Session().commit() | |
122 | h.flash(_('Default permissions updated successfully'), |
|
128 | h.flash(_('Default permissions updated successfully'), | |
123 | category='success') |
|
129 | category='success') | |
124 |
|
130 | |||
125 | except formencode.Invalid, errors: |
|
131 | except formencode.Invalid, errors: | |
126 | defaults = errors.value |
|
132 | defaults = errors.value | |
127 |
|
133 | |||
128 | return htmlfill.render( |
|
134 | return htmlfill.render( | |
129 | render('admin/permissions/permissions.html'), |
|
135 | render('admin/permissions/permissions.html'), | |
130 | defaults=defaults, |
|
136 | defaults=defaults, | |
131 | errors=errors.error_dict or {}, |
|
137 | errors=errors.error_dict or {}, | |
132 | prefix_error=False, |
|
138 | prefix_error=False, | |
133 | encoding="UTF-8") |
|
139 | encoding="UTF-8") | |
134 | except Exception: |
|
140 | except Exception: | |
135 | log.error(traceback.format_exc()) |
|
141 | log.error(traceback.format_exc()) | |
136 | h.flash(_('error occurred during update of permissions'), |
|
142 | h.flash(_('error occurred during update of permissions'), | |
137 | category='error') |
|
143 | category='error') | |
138 |
|
144 | |||
139 | return redirect(url('edit_permission', id=id)) |
|
145 | return redirect(url('edit_permission', id=id)) | |
140 |
|
146 | |||
@@ -157,10 +163,11 b' class PermissionsController(BaseControll' | |||||
157 |
|
163 | |||
158 | #this form can only edit default user permissions |
|
164 | #this form can only edit default user permissions | |
159 | if id == 'default': |
|
165 | if id == 'default': | |
160 | default_user = User.get_by_username('default') |
|
166 | c.user = default_user = User.get_by_username('default') | |
161 |
defaults = {' |
|
167 | defaults = {'anonymous': default_user.active} | |
162 | 'anonymous': default_user.active} |
|
168 | c.perm_user = AuthUser(user_id=default_user.user_id) | |
163 |
|
169 | c.user_ip_map = UserIpMap.query()\ | ||
|
170 | .filter(UserIpMap.user == default_user).all() | |||
164 | for p in default_user.user_perms: |
|
171 | for p in default_user.user_perms: | |
165 | if p.permission.permission_name.startswith('repository.'): |
|
172 | if p.permission.permission_name.startswith('repository.'): | |
166 | defaults['default_repo_perm'] = p.permission.permission_name |
|
173 | defaults['default_repo_perm'] = p.permission.permission_name | |
@@ -181,7 +188,7 b' class PermissionsController(BaseControll' | |||||
181 | render('admin/permissions/permissions.html'), |
|
188 | render('admin/permissions/permissions.html'), | |
182 | defaults=defaults, |
|
189 | defaults=defaults, | |
183 | encoding="UTF-8", |
|
190 | encoding="UTF-8", | |
184 |
force_defaults= |
|
191 | force_defaults=False | |
185 | ) |
|
192 | ) | |
186 | else: |
|
193 | else: | |
187 | return redirect(url('admin_home')) |
|
194 | return redirect(url('admin_home')) |
@@ -135,40 +135,10 b' class ReposController(BaseController):' | |||||
135 | .order_by(func.lower(Repository.repo_name))\ |
|
135 | .order_by(func.lower(Repository.repo_name))\ | |
136 | .all() |
|
136 | .all() | |
137 |
|
137 | |||
138 | repos_data = [] |
|
138 | repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list, | |
139 | total_records = len(c.repos_list) |
|
139 | admin=True) | |
140 |
|
140 | #json used to render the grid | ||
141 | _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup |
|
141 | c.data = json.dumps(repos_data) | |
142 | template = _tmpl_lookup.get_template('data_table/_dt_elements.html') |
|
|||
143 |
|
||||
144 | quick_menu = lambda repo_name: (template.get_def("quick_menu") |
|
|||
145 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
146 | repo_lnk = lambda name, rtype, private, fork_of: ( |
|
|||
147 | template.get_def("repo_name") |
|
|||
148 | .render(name, rtype, private, fork_of, short_name=False, |
|
|||
149 | admin=True, _=_, h=h, c=c)) |
|
|||
150 |
|
||||
151 | repo_actions = lambda repo_name: (template.get_def("repo_actions") |
|
|||
152 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
153 |
|
||||
154 | for repo in c.repos_list: |
|
|||
155 | repos_data.append({ |
|
|||
156 | "menu": quick_menu(repo.repo_name), |
|
|||
157 | "raw_name": repo.repo_name.lower(), |
|
|||
158 | "name": repo_lnk(repo.repo_name, repo.repo_type, |
|
|||
159 | repo.private, repo.fork), |
|
|||
160 | "desc": repo.description, |
|
|||
161 | "owner": repo.user.username, |
|
|||
162 | "action": repo_actions(repo.repo_name), |
|
|||
163 | }) |
|
|||
164 |
|
||||
165 | c.data = json.dumps({ |
|
|||
166 | "totalRecords": total_records, |
|
|||
167 | "startIndex": 0, |
|
|||
168 | "sort": "name", |
|
|||
169 | "dir": "asc", |
|
|||
170 | "records": repos_data |
|
|||
171 | }) |
|
|||
172 |
|
142 | |||
173 | return render('admin/repos/repos.html') |
|
143 | return render('admin/repos/repos.html') | |
174 |
|
144 |
@@ -295,54 +295,18 b' class ReposGroupsController(BaseControll' | |||||
295 | c.groups = self.scm_model.get_repos_groups(groups) |
|
295 | c.groups = self.scm_model.get_repos_groups(groups) | |
296 |
|
296 | |||
297 | if c.visual.lightweight_dashboard is False: |
|
297 | if c.visual.lightweight_dashboard is False: | |
298 |
c. |
|
298 | c.repos_list = self.scm_model.get_repos(all_repos=gr_filter) | |
299 |
|
||||
300 | c.repos_list = c.cached_repo_list |
|
|||
301 | ## lightweight version of dashboard |
|
299 | ## lightweight version of dashboard | |
302 | else: |
|
300 | else: | |
303 | c.repos_list = Repository.query()\ |
|
301 | c.repos_list = Repository.query()\ | |
304 | .filter(Repository.group_id == id)\ |
|
302 | .filter(Repository.group_id == id)\ | |
305 | .order_by(func.lower(Repository.repo_name))\ |
|
303 | .order_by(func.lower(Repository.repo_name))\ | |
306 | .all() |
|
304 | .all() | |
307 | repos_data = [] |
|
|||
308 | total_records = len(c.repos_list) |
|
|||
309 |
|
305 | |||
310 | _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup |
|
306 | repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list, | |
311 | template = _tmpl_lookup.get_template('data_table/_dt_elements.html') |
|
307 | admin=False) | |
312 |
|
308 | #json used to render the grid | ||
313 | quick_menu = lambda repo_name: (template.get_def("quick_menu") |
|
309 | c.data = json.dumps(repos_data) | |
314 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
315 | repo_lnk = lambda name, rtype, private, fork_of: ( |
|
|||
316 | template.get_def("repo_name") |
|
|||
317 | .render(name, rtype, private, fork_of, short_name=False, |
|
|||
318 | admin=False, _=_, h=h, c=c)) |
|
|||
319 | last_change = lambda last_change: (template.get_def("last_change") |
|
|||
320 | .render(last_change, _=_, h=h, c=c)) |
|
|||
321 | rss_lnk = lambda repo_name: (template.get_def("rss") |
|
|||
322 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
323 | atom_lnk = lambda repo_name: (template.get_def("atom") |
|
|||
324 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
325 |
|
||||
326 | for repo in c.repos_list: |
|
|||
327 | repos_data.append({ |
|
|||
328 | "menu": quick_menu(repo.repo_name), |
|
|||
329 | "raw_name": repo.repo_name.lower(), |
|
|||
330 | "name": repo_lnk(repo.repo_name, repo.repo_type, |
|
|||
331 | repo.private, repo.fork), |
|
|||
332 | "last_change": last_change(repo.last_db_change), |
|
|||
333 | "desc": repo.description, |
|
|||
334 | "owner": h.person(repo.user.username), |
|
|||
335 | "rss": rss_lnk(repo.repo_name), |
|
|||
336 | "atom": atom_lnk(repo.repo_name), |
|
|||
337 | }) |
|
|||
338 |
|
||||
339 | c.data = json.dumps({ |
|
|||
340 | "totalRecords": total_records, |
|
|||
341 | "startIndex": 0, |
|
|||
342 | "sort": "name", |
|
|||
343 | "dir": "asc", |
|
|||
344 | "records": repos_data |
|
|||
345 | }) |
|
|||
346 |
|
310 | |||
347 | return render('admin/repos_groups/repos_groups.html') |
|
311 | return render('admin/repos_groups/repos_groups.html') | |
348 |
|
312 |
@@ -48,11 +48,12 b' from rhodecode.model.forms import UserFo' | |||||
48 | ApplicationUiSettingsForm, ApplicationVisualisationForm |
|
48 | ApplicationUiSettingsForm, ApplicationVisualisationForm | |
49 | from rhodecode.model.scm import ScmModel |
|
49 | from rhodecode.model.scm import ScmModel | |
50 | from rhodecode.model.user import UserModel |
|
50 | from rhodecode.model.user import UserModel | |
|
51 | from rhodecode.model.repo import RepoModel | |||
51 | from rhodecode.model.db import User |
|
52 | from rhodecode.model.db import User | |
52 | from rhodecode.model.notification import EmailNotificationModel |
|
53 | from rhodecode.model.notification import EmailNotificationModel | |
53 | from rhodecode.model.meta import Session |
|
54 | from rhodecode.model.meta import Session | |
54 | from rhodecode.lib.utils2 import str2bool |
|
55 | from rhodecode.lib.utils2 import str2bool, safe_unicode | |
55 |
|
56 | from rhodecode.lib.compat import json | ||
56 | log = logging.getLogger(__name__) |
|
57 | log = logging.getLogger(__name__) | |
57 |
|
58 | |||
58 |
|
59 | |||
@@ -119,10 +120,11 b' class SettingsController(BaseController)' | |||||
119 | invalidate_cache('get_repo_cached_%s' % repo_name) |
|
120 | invalidate_cache('get_repo_cached_%s' % repo_name) | |
120 |
|
121 | |||
121 | added, removed = repo2db_mapper(initial, rm_obsolete) |
|
122 | added, removed = repo2db_mapper(initial, rm_obsolete) | |
122 |
|
123 | _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-' | ||
123 | h.flash(_('Repositories successfully' |
|
124 | h.flash(_('Repositories successfully ' | |
124 |
' |
|
125 | 'rescanned added: %s ; removed: %s') % | |
125 | category='success') |
|
126 | (_repr(added), _repr(removed)), | |
|
127 | category='success') | |||
126 |
|
128 | |||
127 | if setting_id == 'whoosh': |
|
129 | if setting_id == 'whoosh': | |
128 | repo_location = self._get_hg_ui_settings()['paths_root_path'] |
|
130 | repo_location = self._get_hg_ui_settings()['paths_root_path'] | |
@@ -336,7 +338,7 b' class SettingsController(BaseController)' | |||||
336 | .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT, |
|
338 | .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT, | |
337 | body=test_email_body) |
|
339 | body=test_email_body) | |
338 |
|
340 | |||
339 |
recipients = [test_email] if |
|
341 | recipients = [test_email] if test_email else None | |
340 |
|
342 | |||
341 | run_task(tasks.send_email, recipients, test_email_subj, |
|
343 | run_task(tasks.send_email, recipients, test_email_subj, | |
342 | test_email_body, test_email_html_body) |
|
344 | test_email_body, test_email_html_body) | |
@@ -381,6 +383,17 b' class SettingsController(BaseController)' | |||||
381 | force_defaults=False |
|
383 | force_defaults=False | |
382 | ) |
|
384 | ) | |
383 |
|
385 | |||
|
386 | def _load_my_repos_data(self): | |||
|
387 | repos_list = Session().query(Repository)\ | |||
|
388 | .filter(Repository.user_id == | |||
|
389 | self.rhodecode_user.user_id)\ | |||
|
390 | .order_by(func.lower(Repository.repo_name)).all() | |||
|
391 | ||||
|
392 | repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, | |||
|
393 | admin=True) | |||
|
394 | #json used to render the grid | |||
|
395 | return json.dumps(repos_data) | |||
|
396 | ||||
384 | @NotAnonymous() |
|
397 | @NotAnonymous() | |
385 | def my_account(self): |
|
398 | def my_account(self): | |
386 | """ |
|
399 | """ | |
@@ -389,17 +402,16 b' class SettingsController(BaseController)' | |||||
389 | # url('admin_settings_my_account') |
|
402 | # url('admin_settings_my_account') | |
390 |
|
403 | |||
391 | c.user = User.get(self.rhodecode_user.user_id) |
|
404 | c.user = User.get(self.rhodecode_user.user_id) | |
392 | all_repos = Session().query(Repository)\ |
|
405 | c.ldap_dn = c.user.ldap_dn | |
393 | .filter(Repository.user_id == c.user.user_id)\ |
|
|||
394 | .order_by(func.lower(Repository.repo_name)).all() |
|
|||
395 |
|
||||
396 | c.user_repos = ScmModel().get_repos(all_repos) |
|
|||
397 |
|
406 | |||
398 | if c.user.username == 'default': |
|
407 | if c.user.username == 'default': | |
399 | h.flash(_("You can't edit this user since it's" |
|
408 | h.flash(_("You can't edit this user since it's" | |
400 | " crucial for entire application"), category='warning') |
|
409 | " crucial for entire application"), category='warning') | |
401 | return redirect(url('users')) |
|
410 | return redirect(url('users')) | |
402 |
|
411 | |||
|
412 | #json used to render the grid | |||
|
413 | c.data = self._load_my_repos_data() | |||
|
414 | ||||
403 | defaults = c.user.get_dict() |
|
415 | defaults = c.user.get_dict() | |
404 |
|
416 | |||
405 | c.form = htmlfill.render( |
|
417 | c.form = htmlfill.render( | |
@@ -420,19 +432,25 b' class SettingsController(BaseController)' | |||||
420 | # method='put') |
|
432 | # method='put') | |
421 | # url('admin_settings_my_account_update', id=ID) |
|
433 | # url('admin_settings_my_account_update', id=ID) | |
422 | uid = self.rhodecode_user.user_id |
|
434 | uid = self.rhodecode_user.user_id | |
|
435 | c.user = User.get(self.rhodecode_user.user_id) | |||
|
436 | c.ldap_dn = c.user.ldap_dn | |||
423 | email = self.rhodecode_user.email |
|
437 | email = self.rhodecode_user.email | |
424 | _form = UserForm(edit=True, |
|
438 | _form = UserForm(edit=True, | |
425 | old_data={'user_id': uid, 'email': email})() |
|
439 | old_data={'user_id': uid, 'email': email})() | |
426 | form_result = {} |
|
440 | form_result = {} | |
427 | try: |
|
441 | try: | |
428 | form_result = _form.to_python(dict(request.POST)) |
|
442 | form_result = _form.to_python(dict(request.POST)) | |
429 | UserModel().update_my_account(uid, form_result) |
|
443 | skip_attrs = ['admin', 'active'] # skip attr for my account | |
|
444 | if c.ldap_dn: | |||
|
445 | #forbid updating username for ldap accounts | |||
|
446 | skip_attrs.append('username') | |||
|
447 | UserModel().update(uid, form_result, skip_attrs=skip_attrs) | |||
430 | h.flash(_('Your account was updated successfully'), |
|
448 | h.flash(_('Your account was updated successfully'), | |
431 | category='success') |
|
449 | category='success') | |
432 | Session().commit() |
|
450 | Session().commit() | |
433 | except formencode.Invalid, errors: |
|
451 | except formencode.Invalid, errors: | |
434 | c.user = User.get(self.rhodecode_user.user_id) |
|
452 | #json used to render the grid | |
435 |
|
453 | c.data = self._load_my_repos_data() | ||
436 | c.form = htmlfill.render( |
|
454 | c.form = htmlfill.render( | |
437 | render('admin/users/user_edit_my_account_form.html'), |
|
455 | render('admin/users/user_edit_my_account_form.html'), | |
438 | defaults=errors.value, |
|
456 | defaults=errors.value, | |
@@ -448,23 +466,14 b' class SettingsController(BaseController)' | |||||
448 | return redirect(url('my_account')) |
|
466 | return redirect(url('my_account')) | |
449 |
|
467 | |||
450 | @NotAnonymous() |
|
468 | @NotAnonymous() | |
451 | def my_account_my_repos(self): |
|
|||
452 | all_repos = Session().query(Repository)\ |
|
|||
453 | .filter(Repository.user_id == self.rhodecode_user.user_id)\ |
|
|||
454 | .order_by(func.lower(Repository.repo_name))\ |
|
|||
455 | .all() |
|
|||
456 | c.user_repos = ScmModel().get_repos(all_repos) |
|
|||
457 | return render('admin/users/user_edit_my_account_repos.html') |
|
|||
458 |
|
||||
459 | @NotAnonymous() |
|
|||
460 | def my_account_my_pullrequests(self): |
|
469 | def my_account_my_pullrequests(self): | |
461 | c.my_pull_requests = PullRequest.query()\ |
|
470 | c.my_pull_requests = PullRequest.query()\ | |
462 | .filter(PullRequest.user_id== |
|
471 | .filter(PullRequest.user_id == | |
463 | self.rhodecode_user.user_id)\ |
|
472 | self.rhodecode_user.user_id)\ | |
464 | .all() |
|
473 | .all() | |
465 | c.participate_in_pull_requests = \ |
|
474 | c.participate_in_pull_requests = \ | |
466 | [x.pull_request for x in PullRequestReviewers.query()\ |
|
475 | [x.pull_request for x in PullRequestReviewers.query()\ | |
467 | .filter(PullRequestReviewers.user_id== |
|
476 | .filter(PullRequestReviewers.user_id == | |
468 | self.rhodecode_user.user_id)\ |
|
477 | self.rhodecode_user.user_id)\ | |
469 | .all()] |
|
478 | .all()] | |
470 | return render('admin/users/user_edit_my_account_pullrequests.html') |
|
479 | return render('admin/users/user_edit_my_account_pullrequests.html') |
@@ -41,7 +41,7 b' from rhodecode.lib.auth import LoginRequ' | |||||
41 | AuthUser |
|
41 | AuthUser | |
42 | from rhodecode.lib.base import BaseController, render |
|
42 | from rhodecode.lib.base import BaseController, render | |
43 |
|
43 | |||
44 | from rhodecode.model.db import User, UserEmailMap |
|
44 | from rhodecode.model.db import User, UserEmailMap, UserIpMap | |
45 | from rhodecode.model.forms import UserForm |
|
45 | from rhodecode.model.forms import UserForm | |
46 | from rhodecode.model.user import UserModel |
|
46 | from rhodecode.model.user import UserModel | |
47 | from rhodecode.model.meta import Session |
|
47 | from rhodecode.model.meta import Session | |
@@ -159,7 +159,7 b' class UsersController(BaseController):' | |||||
159 | user_model = UserModel() |
|
159 | user_model = UserModel() | |
160 | c.user = user_model.get(id) |
|
160 | c.user = user_model.get(id) | |
161 | c.ldap_dn = c.user.ldap_dn |
|
161 | c.ldap_dn = c.user.ldap_dn | |
162 | c.perm_user = AuthUser(user_id=id) |
|
162 | c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr) | |
163 | _form = UserForm(edit=True, old_data={'user_id': id, |
|
163 | _form = UserForm(edit=True, old_data={'user_id': id, | |
164 | 'email': c.user.email})() |
|
164 | 'email': c.user.email})() | |
165 | form_result = {} |
|
165 | form_result = {} | |
@@ -178,6 +178,8 b' class UsersController(BaseController):' | |||||
178 | except formencode.Invalid, errors: |
|
178 | except formencode.Invalid, errors: | |
179 | c.user_email_map = UserEmailMap.query()\ |
|
179 | c.user_email_map = UserEmailMap.query()\ | |
180 | .filter(UserEmailMap.user == c.user).all() |
|
180 | .filter(UserEmailMap.user == c.user).all() | |
|
181 | c.user_ip_map = UserIpMap.query()\ | |||
|
182 | .filter(UserIpMap.user == c.user).all() | |||
181 | defaults = errors.value |
|
183 | defaults = errors.value | |
182 | e = errors.error_dict or {} |
|
184 | e = errors.error_dict or {} | |
183 | defaults.update({ |
|
185 | defaults.update({ | |
@@ -231,12 +233,14 b' class UsersController(BaseController):' | |||||
231 | h.flash(_("You can't edit this user"), category='warning') |
|
233 | h.flash(_("You can't edit this user"), category='warning') | |
232 | return redirect(url('users')) |
|
234 | return redirect(url('users')) | |
233 |
|
235 | |||
234 | c.perm_user = AuthUser(user_id=id) |
|
236 | c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr) | |
235 | c.user.permissions = {} |
|
237 | c.user.permissions = {} | |
236 | c.granted_permissions = UserModel().fill_perms(c.user)\ |
|
238 | c.granted_permissions = UserModel().fill_perms(c.user)\ | |
237 | .permissions['global'] |
|
239 | .permissions['global'] | |
238 | c.user_email_map = UserEmailMap.query()\ |
|
240 | c.user_email_map = UserEmailMap.query()\ | |
239 | .filter(UserEmailMap.user == c.user).all() |
|
241 | .filter(UserEmailMap.user == c.user).all() | |
|
242 | c.user_ip_map = UserIpMap.query()\ | |||
|
243 | .filter(UserIpMap.user == c.user).all() | |||
240 | user_model = UserModel() |
|
244 | user_model = UserModel() | |
241 | c.ldap_dn = c.user.ldap_dn |
|
245 | c.ldap_dn = c.user.ldap_dn | |
242 | defaults = c.user.get_dict() |
|
246 | defaults = c.user.get_dict() | |
@@ -299,7 +303,6 b' class UsersController(BaseController):' | |||||
299 | """POST /user_emails:Add an existing item""" |
|
303 | """POST /user_emails:Add an existing item""" | |
300 | # url('user_emails', id=ID, method='put') |
|
304 | # url('user_emails', id=ID, method='put') | |
301 |
|
305 | |||
302 | #TODO: validation and form !!! |
|
|||
303 | email = request.POST.get('new_email') |
|
306 | email = request.POST.get('new_email') | |
304 | user_model = UserModel() |
|
307 | user_model = UserModel() | |
305 |
|
308 | |||
@@ -324,3 +327,36 b' class UsersController(BaseController):' | |||||
324 | Session().commit() |
|
327 | Session().commit() | |
325 | h.flash(_("Removed email from user"), category='success') |
|
328 | h.flash(_("Removed email from user"), category='success') | |
326 | return redirect(url('edit_user', id=id)) |
|
329 | return redirect(url('edit_user', id=id)) | |
|
330 | ||||
|
331 | def add_ip(self, id): | |||
|
332 | """POST /user_ips:Add an existing item""" | |||
|
333 | # url('user_ips', id=ID, method='put') | |||
|
334 | ||||
|
335 | ip = request.POST.get('new_ip') | |||
|
336 | user_model = UserModel() | |||
|
337 | ||||
|
338 | try: | |||
|
339 | user_model.add_extra_ip(id, ip) | |||
|
340 | Session().commit() | |||
|
341 | h.flash(_("Added ip %s to user") % ip, category='success') | |||
|
342 | except formencode.Invalid, error: | |||
|
343 | msg = error.error_dict['ip'] | |||
|
344 | h.flash(msg, category='error') | |||
|
345 | except Exception: | |||
|
346 | log.error(traceback.format_exc()) | |||
|
347 | h.flash(_('An error occurred during ip saving'), | |||
|
348 | category='error') | |||
|
349 | if 'default_user' in request.POST: | |||
|
350 | return redirect(url('edit_permission', id='default')) | |||
|
351 | return redirect(url('edit_user', id=id)) | |||
|
352 | ||||
|
353 | def delete_ip(self, id): | |||
|
354 | """DELETE /user_ips_delete/id: Delete an existing item""" | |||
|
355 | # url('user_ips_delete', id=ID, method='delete') | |||
|
356 | user_model = UserModel() | |||
|
357 | user_model.delete_extra_ip(id, request.POST.get('del_ip')) | |||
|
358 | Session().commit() | |||
|
359 | h.flash(_("Removed ip from user"), category='success') | |||
|
360 | if 'default_user' in request.POST: | |||
|
361 | return redirect(url('edit_permission', id='default')) | |||
|
362 | return redirect(url('edit_user', id=id)) |
@@ -32,17 +32,15 b' import urllib' | |||||
32 | import traceback |
|
32 | import traceback | |
33 | import time |
|
33 | import time | |
34 |
|
34 | |||
35 | from rhodecode.lib.compat import izip_longest, json |
|
|||
36 |
|
||||
37 | from paste.response import replace_header |
|
35 | from paste.response import replace_header | |
38 |
|
||||
39 | from pylons.controllers import WSGIController |
|
36 | from pylons.controllers import WSGIController | |
40 |
|
37 | |||
41 |
|
||||
42 | from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ |
|
38 | from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ | |
43 | HTTPBadRequest, HTTPError |
|
39 | HTTPBadRequest, HTTPError | |
44 |
|
40 | |||
45 | from rhodecode.model.db import User |
|
41 | from rhodecode.model.db import User | |
|
42 | from rhodecode.model import meta | |||
|
43 | from rhodecode.lib.compat import izip_longest, json | |||
46 | from rhodecode.lib.auth import AuthUser |
|
44 | from rhodecode.lib.auth import AuthUser | |
47 | from rhodecode.lib.base import _get_ip_addr, _get_access_path |
|
45 | from rhodecode.lib.base import _get_ip_addr, _get_access_path | |
48 | from rhodecode.lib.utils2 import safe_unicode |
|
46 | from rhodecode.lib.utils2 import safe_unicode | |
@@ -86,6 +84,9 b' class JSONRPCController(WSGIController):' | |||||
86 |
|
84 | |||
87 | """ |
|
85 | """ | |
88 |
|
86 | |||
|
87 | def _get_ip_addr(self, environ): | |||
|
88 | return _get_ip_addr(environ) | |||
|
89 | ||||
89 | def _get_method_args(self): |
|
90 | def _get_method_args(self): | |
90 | """ |
|
91 | """ | |
91 | Return `self._rpc_args` to dispatched controller method |
|
92 | Return `self._rpc_args` to dispatched controller method | |
@@ -99,6 +100,7 b' class JSONRPCController(WSGIController):' | |||||
99 | controller and if it exists, dispatch to it. |
|
100 | controller and if it exists, dispatch to it. | |
100 | """ |
|
101 | """ | |
101 | start = time.time() |
|
102 | start = time.time() | |
|
103 | ip_addr = self.ip_addr = self._get_ip_addr(environ) | |||
102 | self._req_id = None |
|
104 | self._req_id = None | |
103 | if 'CONTENT_LENGTH' not in environ: |
|
105 | if 'CONTENT_LENGTH' not in environ: | |
104 | log.debug("No Content-Length") |
|
106 | log.debug("No Content-Length") | |
@@ -130,6 +132,9 b' class JSONRPCController(WSGIController):' | |||||
130 | self._req_id = json_body['id'] |
|
132 | self._req_id = json_body['id'] | |
131 | self._req_method = json_body['method'] |
|
133 | self._req_method = json_body['method'] | |
132 | self._request_params = json_body['args'] |
|
134 | self._request_params = json_body['args'] | |
|
135 | if not isinstance(self._request_params, dict): | |||
|
136 | self._request_params = {} | |||
|
137 | ||||
133 | log.debug( |
|
138 | log.debug( | |
134 | 'method: %s, params: %s' % (self._req_method, |
|
139 | 'method: %s, params: %s' % (self._req_method, | |
135 | self._request_params) |
|
140 | self._request_params) | |
@@ -144,7 +149,15 b' class JSONRPCController(WSGIController):' | |||||
144 | if u is None: |
|
149 | if u is None: | |
145 | return jsonrpc_error(retid=self._req_id, |
|
150 | return jsonrpc_error(retid=self._req_id, | |
146 | message='Invalid API KEY') |
|
151 | message='Invalid API KEY') | |
147 | auth_u = AuthUser(u.user_id, self._req_api_key) |
|
152 | ||
|
153 | #check if we are allowed to use this IP | |||
|
154 | auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr) | |||
|
155 | if not auth_u.ip_allowed: | |||
|
156 | return jsonrpc_error(retid=self._req_id, | |||
|
157 | message='request from IP:%s not allowed' % (ip_addr)) | |||
|
158 | else: | |||
|
159 | log.info('Access for IP:%s allowed' % (ip_addr)) | |||
|
160 | ||||
148 | except Exception, e: |
|
161 | except Exception, e: | |
149 | return jsonrpc_error(retid=self._req_id, |
|
162 | return jsonrpc_error(retid=self._req_id, | |
150 | message='Invalid API KEY') |
|
163 | message='Invalid API KEY') | |
@@ -202,6 +215,7 b' class JSONRPCController(WSGIController):' | |||||
202 | ) |
|
215 | ) | |
203 |
|
216 | |||
204 | self._rpc_args = {USER_SESSION_ATTR: u} |
|
217 | self._rpc_args = {USER_SESSION_ATTR: u} | |
|
218 | ||||
205 | self._rpc_args.update(self._request_params) |
|
219 | self._rpc_args.update(self._request_params) | |
206 |
|
220 | |||
207 | self._rpc_args['action'] = self._req_method |
|
221 | self._rpc_args['action'] = self._req_method |
@@ -27,10 +27,12 b'' | |||||
27 |
|
27 | |||
28 | import traceback |
|
28 | import traceback | |
29 | import logging |
|
29 | import logging | |
|
30 | from pylons.controllers.util import abort | |||
30 |
|
31 | |||
31 | from rhodecode.controllers.api import JSONRPCController, JSONRPCError |
|
32 | from rhodecode.controllers.api import JSONRPCController, JSONRPCError | |
32 |
from rhodecode.lib.auth import |
|
33 | from rhodecode.lib.auth import PasswordGenerator, AuthUser, \ | |
33 | HasPermissionAnyDecorator, PasswordGenerator, AuthUser |
|
34 | HasPermissionAllDecorator, HasPermissionAnyDecorator, \ | |
|
35 | HasPermissionAnyApi, HasRepoPermissionAnyApi | |||
34 | from rhodecode.lib.utils import map_groups, repo2db_mapper |
|
36 | from rhodecode.lib.utils import map_groups, repo2db_mapper | |
35 | from rhodecode.model.meta import Session |
|
37 | from rhodecode.model.meta import Session | |
36 | from rhodecode.model.scm import ScmModel |
|
38 | from rhodecode.model.scm import ScmModel | |
@@ -38,11 +40,27 b' from rhodecode.model.repo import RepoMod' | |||||
38 | from rhodecode.model.user import UserModel |
|
40 | from rhodecode.model.user import UserModel | |
39 | from rhodecode.model.users_group import UsersGroupModel |
|
41 | from rhodecode.model.users_group import UsersGroupModel | |
40 | from rhodecode.model.permission import PermissionModel |
|
42 | from rhodecode.model.permission import PermissionModel | |
41 | from rhodecode.model.db import Repository |
|
43 | from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap | |
42 |
|
44 | |||
43 | log = logging.getLogger(__name__) |
|
45 | log = logging.getLogger(__name__) | |
44 |
|
46 | |||
45 |
|
47 | |||
|
48 | class OptionalAttr(object): | |||
|
49 | """ | |||
|
50 | Special Optional Option that defines other attribute | |||
|
51 | """ | |||
|
52 | def __init__(self, attr_name): | |||
|
53 | self.attr_name = attr_name | |||
|
54 | ||||
|
55 | def __repr__(self): | |||
|
56 | return '<OptionalAttr:%s>' % self.attr_name | |||
|
57 | ||||
|
58 | def __call__(self): | |||
|
59 | return self | |||
|
60 | #alias | |||
|
61 | OAttr = OptionalAttr | |||
|
62 | ||||
|
63 | ||||
46 | class Optional(object): |
|
64 | class Optional(object): | |
47 | """ |
|
65 | """ | |
48 | Defines an optional parameter:: |
|
66 | Defines an optional parameter:: | |
@@ -184,10 +202,11 b' class ApiController(JSONRPCController):' | |||||
184 | 'Error occurred during rescan repositories action' |
|
202 | 'Error occurred during rescan repositories action' | |
185 | ) |
|
203 | ) | |
186 |
|
204 | |||
187 | @HasPermissionAllDecorator('hg.admin') |
|
205 | def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))): | |
188 | def lock(self, apiuser, repoid, userid, locked): |
|
|||
189 | """ |
|
206 | """ | |
190 | Set locking state on particular repository by given user |
|
207 | Set locking state on particular repository by given user, if | |
|
208 | this command is runned by non-admin account userid is set to user | |||
|
209 | who is calling this method | |||
191 |
|
210 | |||
192 | :param apiuser: |
|
211 | :param apiuser: | |
193 | :param repoid: |
|
212 | :param repoid: | |
@@ -195,6 +214,22 b' class ApiController(JSONRPCController):' | |||||
195 | :param locked: |
|
214 | :param locked: | |
196 | """ |
|
215 | """ | |
197 | repo = get_repo_or_error(repoid) |
|
216 | repo = get_repo_or_error(repoid) | |
|
217 | if HasPermissionAnyApi('hg.admin')(user=apiuser): | |||
|
218 | pass | |||
|
219 | elif HasRepoPermissionAnyApi('repository.admin', | |||
|
220 | 'repository.write')(user=apiuser, | |||
|
221 | repo_name=repo.repo_name): | |||
|
222 | #make sure normal user does not pass someone else userid, | |||
|
223 | #he is not allowed to do that | |||
|
224 | if not isinstance(userid, Optional) and userid != apiuser.user_id: | |||
|
225 | raise JSONRPCError( | |||
|
226 | 'userid is not the same as your user' | |||
|
227 | ) | |||
|
228 | else: | |||
|
229 | raise JSONRPCError('repository `%s` does not exist' % (repoid)) | |||
|
230 | ||||
|
231 | if isinstance(userid, Optional): | |||
|
232 | userid = apiuser.user_id | |||
198 | user = get_user_or_error(userid) |
|
233 | user = get_user_or_error(userid) | |
199 | locked = bool(locked) |
|
234 | locked = bool(locked) | |
200 | try: |
|
235 | try: | |
@@ -212,13 +247,38 b' class ApiController(JSONRPCController):' | |||||
212 | ) |
|
247 | ) | |
213 |
|
248 | |||
214 | @HasPermissionAllDecorator('hg.admin') |
|
249 | @HasPermissionAllDecorator('hg.admin') | |
215 |
def |
|
250 | def show_ip(self, apiuser, userid): | |
216 |
""" |
|
251 | """ | |
217 | Get a user by username |
|
252 | Shows IP address as seen from RhodeCode server, together with all | |
|
253 | defined IP addresses for given user | |||
218 |
|
254 | |||
219 | :param apiuser: |
|
255 | :param apiuser: | |
220 | :param userid: |
|
256 | :param userid: | |
221 | """ |
|
257 | """ | |
|
258 | user = get_user_or_error(userid) | |||
|
259 | ips = UserIpMap.query().filter(UserIpMap.user == user).all() | |||
|
260 | return dict( | |||
|
261 | ip_addr_server=self.ip_addr, | |||
|
262 | user_ips=ips | |||
|
263 | ) | |||
|
264 | ||||
|
265 | def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))): | |||
|
266 | """" | |||
|
267 | Get a user by username, or userid, if userid is given | |||
|
268 | ||||
|
269 | :param apiuser: | |||
|
270 | :param userid: | |||
|
271 | """ | |||
|
272 | if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | |||
|
273 | #make sure normal user does not pass someone else userid, | |||
|
274 | #he is not allowed to do that | |||
|
275 | if not isinstance(userid, Optional) and userid != apiuser.user_id: | |||
|
276 | raise JSONRPCError( | |||
|
277 | 'userid is not the same as your user' | |||
|
278 | ) | |||
|
279 | ||||
|
280 | if isinstance(userid, Optional): | |||
|
281 | userid = apiuser.user_id | |||
222 |
|
282 | |||
223 | user = get_user_or_error(userid) |
|
283 | user = get_user_or_error(userid) | |
224 | data = user.get_api_data() |
|
284 | data = user.get_api_data() | |
@@ -479,7 +539,6 b' class ApiController(JSONRPCController):' | |||||
479 | ) |
|
539 | ) | |
480 | ) |
|
540 | ) | |
481 |
|
541 | |||
482 | @HasPermissionAnyDecorator('hg.admin') |
|
|||
483 | def get_repo(self, apiuser, repoid): |
|
542 | def get_repo(self, apiuser, repoid): | |
484 | """" |
|
543 | """" | |
485 | Get repository by name |
|
544 | Get repository by name | |
@@ -489,6 +548,12 b' class ApiController(JSONRPCController):' | |||||
489 | """ |
|
548 | """ | |
490 | repo = get_repo_or_error(repoid) |
|
549 | repo = get_repo_or_error(repoid) | |
491 |
|
550 | |||
|
551 | if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | |||
|
552 | # check if we have admin permission for this repo ! | |||
|
553 | if HasRepoPermissionAnyApi('repository.admin')(user=apiuser, | |||
|
554 | repo_name=repo.repo_name) is False: | |||
|
555 | raise JSONRPCError('repository `%s` does not exist' % (repoid)) | |||
|
556 | ||||
492 | members = [] |
|
557 | members = [] | |
493 | for user in repo.repo_to_perm: |
|
558 | for user in repo.repo_to_perm: | |
494 | perm = user.permission.permission_name |
|
559 | perm = user.permission.permission_name | |
@@ -510,20 +575,23 b' class ApiController(JSONRPCController):' | |||||
510 | data['members'] = members |
|
575 | data['members'] = members | |
511 | return data |
|
576 | return data | |
512 |
|
577 | |||
513 | @HasPermissionAnyDecorator('hg.admin') |
|
|||
514 | def get_repos(self, apiuser): |
|
578 | def get_repos(self, apiuser): | |
515 | """" |
|
579 | """" | |
516 | Get all repositories |
|
580 | Get all repositories | |
517 |
|
581 | |||
518 | :param apiuser: |
|
582 | :param apiuser: | |
519 | """ |
|
583 | """ | |
|
584 | result = [] | |||
|
585 | if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | |||
|
586 | repos = RepoModel().get_all_user_repos(user=apiuser) | |||
|
587 | else: | |||
|
588 | repos = RepoModel().get_all() | |||
520 |
|
589 | |||
521 | result = [] |
|
590 | for repo in repos: | |
522 | for repo in RepoModel().get_all(): |
|
|||
523 | result.append(repo.get_api_data()) |
|
591 | result.append(repo.get_api_data()) | |
524 | return result |
|
592 | return result | |
525 |
|
593 | |||
526 |
@HasPermissionA |
|
594 | @HasPermissionAllDecorator('hg.admin') | |
527 | def get_repo_nodes(self, apiuser, repoid, revision, root_path, |
|
595 | def get_repo_nodes(self, apiuser, repoid, revision, root_path, | |
528 | ret_type='all'): |
|
596 | ret_type='all'): | |
529 | """ |
|
597 | """ | |
@@ -556,12 +624,16 b' class ApiController(JSONRPCController):' | |||||
556 | ) |
|
624 | ) | |
557 |
|
625 | |||
558 | @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') |
|
626 | @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') | |
559 |
def create_repo(self, apiuser, repo_name, owner |
|
627 | def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')), | |
|
628 | repo_type=Optional('hg'), | |||
560 | description=Optional(''), private=Optional(False), |
|
629 | description=Optional(''), private=Optional(False), | |
561 |
clone_uri=Optional(None), landing_rev=Optional('tip') |
|
630 | clone_uri=Optional(None), landing_rev=Optional('tip'), | |
|
631 | enable_statistics=Optional(False), | |||
|
632 | enable_locking=Optional(False), | |||
|
633 | enable_downloads=Optional(False)): | |||
562 | """ |
|
634 | """ | |
563 | Create repository, if clone_url is given it makes a remote clone |
|
635 | Create repository, if clone_url is given it makes a remote clone | |
564 |
if repo_name is within |
|
636 | if repo_name is within a group name the groups will be created | |
565 | automatically if they aren't present |
|
637 | automatically if they aren't present | |
566 |
|
638 | |||
567 | :param apiuser: |
|
639 | :param apiuser: | |
@@ -573,12 +645,32 b' class ApiController(JSONRPCController):' | |||||
573 | :param clone_uri: |
|
645 | :param clone_uri: | |
574 | :param landing_rev: |
|
646 | :param landing_rev: | |
575 | """ |
|
647 | """ | |
|
648 | if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | |||
|
649 | if not isinstance(owner, Optional): | |||
|
650 | #forbid setting owner for non-admins | |||
|
651 | raise JSONRPCError( | |||
|
652 | 'Only RhodeCode admin can specify `owner` param' | |||
|
653 | ) | |||
|
654 | if isinstance(owner, Optional): | |||
|
655 | owner = apiuser.user_id | |||
|
656 | ||||
576 | owner = get_user_or_error(owner) |
|
657 | owner = get_user_or_error(owner) | |
577 |
|
658 | |||
578 | if RepoModel().get_by_repo_name(repo_name): |
|
659 | if RepoModel().get_by_repo_name(repo_name): | |
579 | raise JSONRPCError("repo `%s` already exist" % repo_name) |
|
660 | raise JSONRPCError("repo `%s` already exist" % repo_name) | |
580 |
|
661 | |||
581 | private = Optional.extract(private) |
|
662 | defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) | |
|
663 | if isinstance(private, Optional): | |||
|
664 | private = defs.get('repo_private') or Optional.extract(private) | |||
|
665 | if isinstance(repo_type, Optional): | |||
|
666 | repo_type = defs.get('repo_type') | |||
|
667 | if isinstance(enable_statistics, Optional): | |||
|
668 | enable_statistics = defs.get('repo_enable_statistics') | |||
|
669 | if isinstance(enable_locking, Optional): | |||
|
670 | enable_locking = defs.get('repo_enable_locking') | |||
|
671 | if isinstance(enable_downloads, Optional): | |||
|
672 | enable_downloads = defs.get('repo_enable_downloads') | |||
|
673 | ||||
582 | clone_uri = Optional.extract(clone_uri) |
|
674 | clone_uri = Optional.extract(clone_uri) | |
583 | description = Optional.extract(description) |
|
675 | description = Optional.extract(description) | |
584 | landing_rev = Optional.extract(landing_rev) |
|
676 | landing_rev = Optional.extract(landing_rev) | |
@@ -596,32 +688,51 b' class ApiController(JSONRPCController):' | |||||
596 | clone_uri=clone_uri, |
|
688 | clone_uri=clone_uri, | |
597 | repos_group=group, |
|
689 | repos_group=group, | |
598 | landing_rev=landing_rev, |
|
690 | landing_rev=landing_rev, | |
|
691 | enable_statistics=enable_statistics, | |||
|
692 | enable_downloads=enable_downloads, | |||
|
693 | enable_locking=enable_locking | |||
599 | ) |
|
694 | ) | |
600 |
|
695 | |||
601 | Session().commit() |
|
696 | Session().commit() | |
602 |
|
||||
603 | return dict( |
|
697 | return dict( | |
604 | msg="Created new repository `%s`" % (repo.repo_name), |
|
698 | msg="Created new repository `%s`" % (repo.repo_name), | |
605 | repo=repo.get_api_data() |
|
699 | repo=repo.get_api_data() | |
606 | ) |
|
700 | ) | |
607 |
|
||||
608 | except Exception: |
|
701 | except Exception: | |
609 | log.error(traceback.format_exc()) |
|
702 | log.error(traceback.format_exc()) | |
610 | raise JSONRPCError('failed to create repository `%s`' % repo_name) |
|
703 | raise JSONRPCError('failed to create repository `%s`' % repo_name) | |
611 |
|
704 | |||
612 | @HasPermissionAnyDecorator('hg.admin') |
|
705 | @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository') | |
613 | def fork_repo(self, apiuser, repoid, fork_name, owner, |
|
706 | def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')), | |
614 | description=Optional(''), copy_permissions=Optional(False), |
|
707 | description=Optional(''), copy_permissions=Optional(False), | |
615 | private=Optional(False), landing_rev=Optional('tip')): |
|
708 | private=Optional(False), landing_rev=Optional('tip')): | |
616 | repo = get_repo_or_error(repoid) |
|
709 | repo = get_repo_or_error(repoid) | |
617 | repo_name = repo.repo_name |
|
710 | repo_name = repo.repo_name | |
618 | owner = get_user_or_error(owner) |
|
|||
619 |
|
711 | |||
620 | _repo = RepoModel().get_by_repo_name(fork_name) |
|
712 | _repo = RepoModel().get_by_repo_name(fork_name) | |
621 | if _repo: |
|
713 | if _repo: | |
622 | type_ = 'fork' if _repo.fork else 'repo' |
|
714 | type_ = 'fork' if _repo.fork else 'repo' | |
623 | raise JSONRPCError("%s `%s` already exist" % (type_, fork_name)) |
|
715 | raise JSONRPCError("%s `%s` already exist" % (type_, fork_name)) | |
624 |
|
716 | |||
|
717 | if HasPermissionAnyApi('hg.admin')(user=apiuser): | |||
|
718 | pass | |||
|
719 | elif HasRepoPermissionAnyApi('repository.admin', | |||
|
720 | 'repository.write', | |||
|
721 | 'repository.read')(user=apiuser, | |||
|
722 | repo_name=repo.repo_name): | |||
|
723 | if not isinstance(owner, Optional): | |||
|
724 | #forbid setting owner for non-admins | |||
|
725 | raise JSONRPCError( | |||
|
726 | 'Only RhodeCode admin can specify `owner` param' | |||
|
727 | ) | |||
|
728 | else: | |||
|
729 | raise JSONRPCError('repository `%s` does not exist' % (repoid)) | |||
|
730 | ||||
|
731 | if isinstance(owner, Optional): | |||
|
732 | owner = apiuser.user_id | |||
|
733 | ||||
|
734 | owner = get_user_or_error(owner) | |||
|
735 | ||||
625 | try: |
|
736 | try: | |
626 | # create structure of groups and return the last group |
|
737 | # create structure of groups and return the last group | |
627 | group = map_groups(fork_name) |
|
738 | group = map_groups(fork_name) | |
@@ -652,7 +763,6 b' class ApiController(JSONRPCController):' | |||||
652 | fork_name) |
|
763 | fork_name) | |
653 | ) |
|
764 | ) | |
654 |
|
765 | |||
655 | @HasPermissionAnyDecorator('hg.admin') |
|
|||
656 | def delete_repo(self, apiuser, repoid): |
|
766 | def delete_repo(self, apiuser, repoid): | |
657 | """ |
|
767 | """ | |
658 | Deletes a given repository |
|
768 | Deletes a given repository | |
@@ -662,6 +772,12 b' class ApiController(JSONRPCController):' | |||||
662 | """ |
|
772 | """ | |
663 | repo = get_repo_or_error(repoid) |
|
773 | repo = get_repo_or_error(repoid) | |
664 |
|
774 | |||
|
775 | if HasPermissionAnyApi('hg.admin')(user=apiuser) is False: | |||
|
776 | # check if we have admin permission for this repo ! | |||
|
777 | if HasRepoPermissionAnyApi('repository.admin')(user=apiuser, | |||
|
778 | repo_name=repo.repo_name) is False: | |||
|
779 | raise JSONRPCError('repository `%s` does not exist' % (repoid)) | |||
|
780 | ||||
665 | try: |
|
781 | try: | |
666 | RepoModel().delete(repo) |
|
782 | RepoModel().delete(repo) | |
667 | Session().commit() |
|
783 | Session().commit() | |
@@ -675,7 +791,7 b' class ApiController(JSONRPCController):' | |||||
675 | 'failed to delete repository `%s`' % repo.repo_name |
|
791 | 'failed to delete repository `%s`' % repo.repo_name | |
676 | ) |
|
792 | ) | |
677 |
|
793 | |||
678 |
@HasPermissionA |
|
794 | @HasPermissionAllDecorator('hg.admin') | |
679 | def grant_user_permission(self, apiuser, repoid, userid, perm): |
|
795 | def grant_user_permission(self, apiuser, repoid, userid, perm): | |
680 | """ |
|
796 | """ | |
681 | Grant permission for user on given repository, or update existing one |
|
797 | Grant permission for user on given repository, or update existing one | |
@@ -708,7 +824,7 b' class ApiController(JSONRPCController):' | |||||
708 | ) |
|
824 | ) | |
709 | ) |
|
825 | ) | |
710 |
|
826 | |||
711 |
@HasPermissionA |
|
827 | @HasPermissionAllDecorator('hg.admin') | |
712 | def revoke_user_permission(self, apiuser, repoid, userid): |
|
828 | def revoke_user_permission(self, apiuser, repoid, userid): | |
713 | """ |
|
829 | """ | |
714 | Revoke permission for user on given repository |
|
830 | Revoke permission for user on given repository | |
@@ -739,7 +855,7 b' class ApiController(JSONRPCController):' | |||||
739 | ) |
|
855 | ) | |
740 | ) |
|
856 | ) | |
741 |
|
857 | |||
742 |
@HasPermissionA |
|
858 | @HasPermissionAllDecorator('hg.admin') | |
743 | def grant_users_group_permission(self, apiuser, repoid, usersgroupid, |
|
859 | def grant_users_group_permission(self, apiuser, repoid, usersgroupid, | |
744 | perm): |
|
860 | perm): | |
745 | """ |
|
861 | """ | |
@@ -778,7 +894,7 b' class ApiController(JSONRPCController):' | |||||
778 | ) |
|
894 | ) | |
779 | ) |
|
895 | ) | |
780 |
|
896 | |||
781 |
@HasPermissionA |
|
897 | @HasPermissionAllDecorator('hg.admin') | |
782 | def revoke_users_group_permission(self, apiuser, repoid, usersgroupid): |
|
898 | def revoke_users_group_permission(self, apiuser, repoid, usersgroupid): | |
783 | """ |
|
899 | """ | |
784 | Revoke permission for users group on given repository |
|
900 | Revoke permission for users group on given repository |
@@ -221,13 +221,25 b' class ChangesetController(BaseRepoContro' | |||||
221 | for changeset in c.cs_ranges: |
|
221 | for changeset in c.cs_ranges: | |
222 | inlines = [] |
|
222 | inlines = [] | |
223 | if method == 'show': |
|
223 | if method == 'show': | |
224 |
c.statuses.extend([ChangesetStatusModel() |
|
224 | c.statuses.extend([ChangesetStatusModel().get_status( | |
225 |
|
|
225 | c.rhodecode_db_repo.repo_id, changeset.raw_id)]) | |
226 | changeset.raw_id)]) |
|
|||
227 |
|
226 | |||
228 | c.comments.extend(ChangesetCommentsModel()\ |
|
227 | c.comments.extend(ChangesetCommentsModel()\ | |
229 | .get_comments(c.rhodecode_db_repo.repo_id, |
|
228 | .get_comments(c.rhodecode_db_repo.repo_id, | |
230 | revision=changeset.raw_id)) |
|
229 | revision=changeset.raw_id)) | |
|
230 | ||||
|
231 | #comments from PR | |||
|
232 | st = ChangesetStatusModel().get_statuses( | |||
|
233 | c.rhodecode_db_repo.repo_id, changeset.raw_id, | |||
|
234 | with_revisions=True) | |||
|
235 | # from associated statuses, check the pull requests, and | |||
|
236 | # show comments from them | |||
|
237 | ||||
|
238 | prs = set([x.pull_request for x in | |||
|
239 | filter(lambda x: x.pull_request != None, st)]) | |||
|
240 | ||||
|
241 | for pr in prs: | |||
|
242 | c.comments.extend(pr.comments) | |||
231 | inlines = ChangesetCommentsModel()\ |
|
243 | inlines = ChangesetCommentsModel()\ | |
232 | .get_inline_comments(c.rhodecode_db_repo.repo_id, |
|
244 | .get_inline_comments(c.rhodecode_db_repo.repo_id, | |
233 | revision=changeset.raw_id) |
|
245 | revision=changeset.raw_id) | |
@@ -269,6 +281,9 b' class ChangesetController(BaseRepoContro' | |||||
269 | cs_changes[''] = [None, None, None, None, diff, None] |
|
281 | cs_changes[''] = [None, None, None, None, diff, None] | |
270 | c.changes[changeset.raw_id] = cs_changes |
|
282 | c.changes[changeset.raw_id] = cs_changes | |
271 |
|
283 | |||
|
284 | #sort comments by how they were generated | |||
|
285 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) | |||
|
286 | ||||
272 | # count inline comments |
|
287 | # count inline comments | |
273 | for __, lines in c.inline_comments: |
|
288 | for __, lines in c.inline_comments: | |
274 | for comments in lines.values(): |
|
289 | for comments in lines.values(): | |
@@ -342,7 +357,7 b' class ChangesetController(BaseRepoContro' | |||||
342 | ) |
|
357 | ) | |
343 | except StatusChangeOnClosedPullRequestError: |
|
358 | except StatusChangeOnClosedPullRequestError: | |
344 | log.error(traceback.format_exc()) |
|
359 | log.error(traceback.format_exc()) | |
345 | msg = _('Changing status on a changeset associated with' |
|
360 | msg = _('Changing status on a changeset associated with ' | |
346 | 'a closed pull request is not allowed') |
|
361 | 'a closed pull request is not allowed') | |
347 | h.flash(msg, category='warning') |
|
362 | h.flash(msg, category='warning') | |
348 | return redirect(h.url('changeset_home', repo_name=repo_name, |
|
363 | return redirect(h.url('changeset_home', repo_name=repo_name, | |
@@ -371,7 +386,7 b' class ChangesetController(BaseRepoContro' | |||||
371 | @jsonify |
|
386 | @jsonify | |
372 | def delete_comment(self, repo_name, comment_id): |
|
387 | def delete_comment(self, repo_name, comment_id): | |
373 | co = ChangesetComment.get(comment_id) |
|
388 | co = ChangesetComment.get(comment_id) | |
374 |
owner = |
|
389 | owner = co.author.user_id == c.rhodecode_user.user_id | |
375 | if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: |
|
390 | if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: | |
376 | ChangesetCommentsModel().delete(comment=co) |
|
391 | ChangesetCommentsModel().delete(comment=co) | |
377 | Session().commit() |
|
392 | Session().commit() |
@@ -103,8 +103,11 b' class CompareController(BaseRepoControll' | |||||
103 | c.org_repo = org_repo = Repository.get_by_repo_name(org_repo) |
|
103 | c.org_repo = org_repo = Repository.get_by_repo_name(org_repo) | |
104 | c.other_repo = other_repo = Repository.get_by_repo_name(other_repo) |
|
104 | c.other_repo = other_repo = Repository.get_by_repo_name(other_repo) | |
105 |
|
105 | |||
106 |
if c.org_repo is None |
|
106 | if c.org_repo is None: | |
107 |
log.error('Could not f |
|
107 | log.error('Could not find org repo %s' % org_repo) | |
|
108 | raise HTTPNotFound | |||
|
109 | if c.other_repo is None: | |||
|
110 | log.error('Could not find other repo %s' % other_repo) | |||
108 | raise HTTPNotFound |
|
111 | raise HTTPNotFound | |
109 |
|
112 | |||
110 | if c.org_repo != c.other_repo and h.is_git(c.rhodecode_repo): |
|
113 | if c.org_repo != c.other_repo and h.is_git(c.rhodecode_repo): |
@@ -28,6 +28,7 b' import logging' | |||||
28 | from pylons import tmpl_context as c, request |
|
28 | from pylons import tmpl_context as c, request | |
29 | from pylons.i18n.translation import _ |
|
29 | from pylons.i18n.translation import _ | |
30 | from webob.exc import HTTPBadRequest |
|
30 | from webob.exc import HTTPBadRequest | |
|
31 | from sqlalchemy.sql.expression import func | |||
31 |
|
32 | |||
32 | import rhodecode |
|
33 | import rhodecode | |
33 | from rhodecode.lib import helpers as h |
|
34 | from rhodecode.lib import helpers as h | |
@@ -35,7 +36,8 b' from rhodecode.lib.ext_json import json' | |||||
35 | from rhodecode.lib.auth import LoginRequired |
|
36 | from rhodecode.lib.auth import LoginRequired | |
36 | from rhodecode.lib.base import BaseController, render |
|
37 | from rhodecode.lib.base import BaseController, render | |
37 | from rhodecode.model.db import Repository |
|
38 | from rhodecode.model.db import Repository | |
38 | from sqlalchemy.sql.expression import func |
|
39 | from rhodecode.model.repo import RepoModel | |
|
40 | ||||
39 |
|
41 | |||
40 | log = logging.getLogger(__name__) |
|
42 | log = logging.getLogger(__name__) | |
41 |
|
43 | |||
@@ -58,51 +60,11 b' class HomeController(BaseController):' | |||||
58 | .filter(Repository.group_id == None)\ |
|
60 | .filter(Repository.group_id == None)\ | |
59 | .order_by(func.lower(Repository.repo_name))\ |
|
61 | .order_by(func.lower(Repository.repo_name))\ | |
60 | .all() |
|
62 | .all() | |
61 | repos_data = [] |
|
|||
62 | total_records = len(c.repos_list) |
|
|||
63 |
|
63 | |||
64 | _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup |
|
64 | repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list, | |
65 | template = _tmpl_lookup.get_template('data_table/_dt_elements.html') |
|
65 | admin=False) | |
66 |
|
66 | #json used to render the grid | ||
67 | quick_menu = lambda repo_name: (template.get_def("quick_menu") |
|
67 | c.data = json.dumps(repos_data) | |
68 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
69 | repo_lnk = lambda name, rtype, private, fork_of: ( |
|
|||
70 | template.get_def("repo_name") |
|
|||
71 | .render(name, rtype, private, fork_of, short_name=False, |
|
|||
72 | admin=False, _=_, h=h, c=c)) |
|
|||
73 | last_change = lambda last_change: (template.get_def("last_change") |
|
|||
74 | .render(last_change, _=_, h=h, c=c)) |
|
|||
75 | rss_lnk = lambda repo_name: (template.get_def("rss") |
|
|||
76 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
77 | atom_lnk = lambda repo_name: (template.get_def("atom") |
|
|||
78 | .render(repo_name, _=_, h=h, c=c)) |
|
|||
79 |
|
||||
80 | def desc(desc): |
|
|||
81 | if c.visual.stylify_metatags: |
|
|||
82 | return h.urlify_text(h.desc_stylize(h.truncate(desc, 60))) |
|
|||
83 | else: |
|
|||
84 | return h.urlify_text(h.truncate(desc, 60)) |
|
|||
85 |
|
||||
86 | for repo in c.repos_list: |
|
|||
87 | repos_data.append({ |
|
|||
88 | "menu": quick_menu(repo.repo_name), |
|
|||
89 | "raw_name": repo.repo_name.lower(), |
|
|||
90 | "name": repo_lnk(repo.repo_name, repo.repo_type, |
|
|||
91 | repo.private, repo.fork), |
|
|||
92 | "last_change": last_change(repo.last_db_change), |
|
|||
93 | "desc": desc(repo.description), |
|
|||
94 | "owner": h.person(repo.user.username), |
|
|||
95 | "rss": rss_lnk(repo.repo_name), |
|
|||
96 | "atom": atom_lnk(repo.repo_name), |
|
|||
97 | }) |
|
|||
98 |
|
||||
99 | c.data = json.dumps({ |
|
|||
100 | "totalRecords": total_records, |
|
|||
101 | "startIndex": 0, |
|
|||
102 | "sort": "name", |
|
|||
103 | "dir": "asc", |
|
|||
104 | "records": repos_data |
|
|||
105 | }) |
|
|||
106 |
|
68 | |||
107 | return render('/index.html') |
|
69 | return render('/index.html') | |
108 |
|
70 |
@@ -27,6 +27,8 b' from itertools import groupby' | |||||
27 |
|
27 | |||
28 | from sqlalchemy import or_ |
|
28 | from sqlalchemy import or_ | |
29 | from sqlalchemy.orm import joinedload |
|
29 | from sqlalchemy.orm import joinedload | |
|
30 | from sqlalchemy.sql.expression import func | |||
|
31 | ||||
30 | from webhelpers.paginate import Page |
|
32 | from webhelpers.paginate import Page | |
31 | from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed |
|
33 | from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed | |
32 |
|
34 | |||
@@ -39,10 +41,10 b' from rhodecode.lib.auth import LoginRequ' | |||||
39 | from rhodecode.lib.base import BaseController, render |
|
41 | from rhodecode.lib.base import BaseController, render | |
40 | from rhodecode.model.db import UserLog, UserFollowing, Repository, User |
|
42 | from rhodecode.model.db import UserLog, UserFollowing, Repository, User | |
41 | from rhodecode.model.meta import Session |
|
43 | from rhodecode.model.meta import Session | |
42 | from sqlalchemy.sql.expression import func |
|
|||
43 | from rhodecode.model.scm import ScmModel |
|
|||
44 | from rhodecode.lib.utils2 import safe_int, AttributeDict |
|
44 | from rhodecode.lib.utils2 import safe_int, AttributeDict | |
45 | from rhodecode.controllers.admin.admin import _journal_filter |
|
45 | from rhodecode.controllers.admin.admin import _journal_filter | |
|
46 | from rhodecode.model.repo import RepoModel | |||
|
47 | from rhodecode.lib.compat import json | |||
46 |
|
48 | |||
47 | log = logging.getLogger(__name__) |
|
49 | log = logging.getLogger(__name__) | |
48 |
|
50 | |||
@@ -78,18 +80,73 b' class JournalController(BaseController):' | |||||
78 | c.journal_data = render('journal/journal_data.html') |
|
80 | c.journal_data = render('journal/journal_data.html') | |
79 | if request.environ.get('HTTP_X_PARTIAL_XHR'): |
|
81 | if request.environ.get('HTTP_X_PARTIAL_XHR'): | |
80 | return c.journal_data |
|
82 | return c.journal_data | |
81 | return render('journal/journal.html') |
|
83 | ||
|
84 | repos_list = Session().query(Repository)\ | |||
|
85 | .filter(Repository.user_id == | |||
|
86 | self.rhodecode_user.user_id)\ | |||
|
87 | .order_by(func.lower(Repository.repo_name)).all() | |||
|
88 | ||||
|
89 | repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list, | |||
|
90 | admin=True) | |||
|
91 | #json used to render the grid | |||
|
92 | c.data = json.dumps(repos_data) | |||
|
93 | ||||
|
94 | watched_repos_data = [] | |||
|
95 | ||||
|
96 | ## watched repos | |||
|
97 | _render = RepoModel._render_datatable | |||
|
98 | ||||
|
99 | def quick_menu(repo_name): | |||
|
100 | return _render('quick_menu', repo_name) | |||
|
101 | ||||
|
102 | def repo_lnk(name, rtype, private, fork_of): | |||
|
103 | return _render('repo_name', name, rtype, private, fork_of, | |||
|
104 | short_name=False, admin=False) | |||
|
105 | ||||
|
106 | def last_rev(repo_name, cs_cache): | |||
|
107 | return _render('revision', repo_name, cs_cache.get('revision'), | |||
|
108 | cs_cache.get('raw_id'), cs_cache.get('author'), | |||
|
109 | cs_cache.get('message')) | |||
82 |
|
110 | |||
83 | @LoginRequired() |
|
111 | def desc(desc): | |
84 | @NotAnonymous() |
|
112 | from pylons import tmpl_context as c | |
85 | def index_my_repos(self): |
|
113 | if c.visual.stylify_metatags: | |
86 | c.user = User.get(self.rhodecode_user.user_id) |
|
114 | return h.urlify_text(h.desc_stylize(h.truncate(desc, 60))) | |
87 | if request.environ.get('HTTP_X_PARTIAL_XHR'): |
|
115 | else: | |
88 | all_repos = self.sa.query(Repository)\ |
|
116 | return h.urlify_text(h.truncate(desc, 60)) | |
89 | .filter(Repository.user_id == c.user.user_id)\ |
|
117 | ||
90 | .order_by(func.lower(Repository.repo_name)).all() |
|
118 | def repo_actions(repo_name): | |
91 | c.user_repos = ScmModel().get_repos(all_repos) |
|
119 | return _render('repo_actions', repo_name) | |
92 | return render('journal/journal_page_repos.html') |
|
120 | ||
|
121 | def owner_actions(user_id, username): | |||
|
122 | return _render('user_name', user_id, username) | |||
|
123 | ||||
|
124 | def toogle_follow(repo_id): | |||
|
125 | return _render('toggle_follow', repo_id) | |||
|
126 | ||||
|
127 | for entry in c.following: | |||
|
128 | repo = entry.follows_repository | |||
|
129 | cs_cache = repo.changeset_cache | |||
|
130 | row = { | |||
|
131 | "menu": quick_menu(repo.repo_name), | |||
|
132 | "raw_name": repo.repo_name.lower(), | |||
|
133 | "name": repo_lnk(repo.repo_name, repo.repo_type, | |||
|
134 | repo.private, repo.fork), | |||
|
135 | "last_changeset": last_rev(repo.repo_name, cs_cache), | |||
|
136 | "raw_tip": cs_cache.get('revision'), | |||
|
137 | "action": toogle_follow(repo.repo_id) | |||
|
138 | } | |||
|
139 | ||||
|
140 | watched_repos_data.append(row) | |||
|
141 | ||||
|
142 | c.watched_data = json.dumps({ | |||
|
143 | "totalRecords": len(c.following), | |||
|
144 | "startIndex": 0, | |||
|
145 | "sort": "name", | |||
|
146 | "dir": "asc", | |||
|
147 | "records": watched_repos_data | |||
|
148 | }) | |||
|
149 | return render('journal/journal.html') | |||
93 |
|
150 | |||
94 | @LoginRequired(api_access=True) |
|
151 | @LoginRequired(api_access=True) | |
95 | @NotAnonymous() |
|
152 | @NotAnonymous() |
@@ -54,10 +54,9 b' class LoginController(BaseController):' | |||||
54 | def index(self): |
|
54 | def index(self): | |
55 | # redirect if already logged in |
|
55 | # redirect if already logged in | |
56 | c.came_from = request.GET.get('came_from') |
|
56 | c.came_from = request.GET.get('came_from') | |
57 |
|
57 | not_default = self.rhodecode_user.username != 'default' | ||
58 |
i |
|
58 | ip_allowed = self.rhodecode_user.ip_allowed | |
59 | and self.rhodecode_user.username != 'default': |
|
59 | if self.rhodecode_user.is_authenticated and not_default and ip_allowed: | |
60 |
|
||||
61 | return redirect(url('home')) |
|
60 | return redirect(url('home')) | |
62 |
|
61 | |||
63 | if request.POST: |
|
62 | if request.POST: |
@@ -97,7 +97,7 b' class PullrequestsController(BaseRepoCon' | |||||
97 | return repo.branches.keys()[0] |
|
97 | return repo.branches.keys()[0] | |
98 |
|
98 | |||
99 | def _get_is_allowed_change_status(self, pull_request): |
|
99 | def _get_is_allowed_change_status(self, pull_request): | |
100 |
owner = self.rhodecode_user.user_id == pull_request.user_id |
|
100 | owner = self.rhodecode_user.user_id == pull_request.user_id | |
101 | reviewer = self.rhodecode_user.user_id in [x.user_id for x in |
|
101 | reviewer = self.rhodecode_user.user_id in [x.user_id for x in | |
102 | pull_request.reviewers] |
|
102 | pull_request.reviewers] | |
103 | return (self.rhodecode_user.admin or owner or reviewer) |
|
103 | return (self.rhodecode_user.admin or owner or reviewer) | |
@@ -299,7 +299,7 b' class PullrequestsController(BaseRepoCon' | |||||
299 | else EmptyChangeset(), 'raw_id')) |
|
299 | else EmptyChangeset(), 'raw_id')) | |
300 |
|
300 | |||
301 | c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges]) |
|
301 | c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges]) | |
302 |
c.target_repo = |
|
302 | c.target_repo = other_repo.repo_name | |
303 | # defines that we need hidden inputs with changesets |
|
303 | # defines that we need hidden inputs with changesets | |
304 | c.as_form = request.GET.get('as_form', False) |
|
304 | c.as_form = request.GET.get('as_form', False) | |
305 |
|
305 | |||
@@ -339,7 +339,6 b' class PullrequestsController(BaseRepoCon' | |||||
339 | c.users_array = repo_model.get_users_js() |
|
339 | c.users_array = repo_model.get_users_js() | |
340 | c.users_groups_array = repo_model.get_users_groups_js() |
|
340 | c.users_groups_array = repo_model.get_users_groups_js() | |
341 | c.pull_request = PullRequest.get_or_404(pull_request_id) |
|
341 | c.pull_request = PullRequest.get_or_404(pull_request_id) | |
342 | c.target_repo = c.pull_request.org_repo.repo_name |
|
|||
343 | c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request) |
|
342 | c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request) | |
344 | cc_model = ChangesetCommentsModel() |
|
343 | cc_model = ChangesetCommentsModel() | |
345 | cs_model = ChangesetStatusModel() |
|
344 | cs_model = ChangesetStatusModel() | |
@@ -478,7 +477,7 b' class PullrequestsController(BaseRepoCon' | |||||
478 | #don't allow deleting comments on closed pull request |
|
477 | #don't allow deleting comments on closed pull request | |
479 | raise HTTPForbidden() |
|
478 | raise HTTPForbidden() | |
480 |
|
479 | |||
481 |
owner = |
|
480 | owner = co.author.user_id == c.rhodecode_user.user_id | |
482 | if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: |
|
481 | if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: | |
483 | ChangesetCommentsModel().delete(comment=co) |
|
482 | ChangesetCommentsModel().delete(comment=co) | |
484 | Session().commit() |
|
483 | Session().commit() |
1 | NO CONTENT: modified file, binary diff hidden |
|
NO CONTENT: modified file, binary diff hidden |
@@ -13,7 +13,7 b' msgstr ""' | |||||
13 | "Project-Id-Version: RhodeCode 1.2.0\n" |
|
13 | "Project-Id-Version: RhodeCode 1.2.0\n" | |
14 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" |
|
14 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | |
15 | "POT-Creation-Date: 2012-12-14 04:19+0100\n" |
|
15 | "POT-Creation-Date: 2012-12-14 04:19+0100\n" | |
16 |
"PO-Revision-Date: 201 |
|
16 | "PO-Revision-Date: 2013-01-02 01:39+0900\n" | |
17 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
|
17 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | |
18 | "Language-Team: ja <LL@li.org>\n" |
|
18 | "Language-Team: ja <LL@li.org>\n" | |
19 | "Plural-Forms: nplurals=1; plural=0\n" |
|
19 | "Plural-Forms: nplurals=1; plural=0\n" | |
@@ -41,9 +41,9 b' msgstr ""' | |||||
41 |
|
41 | |||
42 | #: rhodecode/controllers/changeset.py:314 |
|
42 | #: rhodecode/controllers/changeset.py:314 | |
43 | #: rhodecode/controllers/pullrequests.py:417 |
|
43 | #: rhodecode/controllers/pullrequests.py:417 | |
44 |
#, |
|
44 | #, python-format | |
45 | msgid "Status change -> %s" |
|
45 | msgid "Status change -> %s" | |
46 | msgstr "" |
|
46 | msgstr "ステータス変更 -> %s" | |
47 |
|
47 | |||
48 | #: rhodecode/controllers/changeset.py:345 |
|
48 | #: rhodecode/controllers/changeset.py:345 | |
49 | msgid "" |
|
49 | msgid "" | |
@@ -71,7 +71,7 b' msgstr "\xe3\x83\xaa\xe3\x82\xbd\xe3\x83\xbc\xe3\x82\xb9\xe3\x81\xab\xe3\x82\xa2\xe3\x82\xaf\xe3\x82\xbb\xe3\x82\xb9\xe3\x81\x99\xe3\x82\x8b\xe6\xa8\xa9\xe9\x99\x90\xe3\x81\x8c\xe3\x81\x82\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x9b\xe3\x82\x93"' | |||||
71 |
|
71 | |||
72 | #: rhodecode/controllers/error.py:103 |
|
72 | #: rhodecode/controllers/error.py:103 | |
73 | msgid "You don't have permission to view this page" |
|
73 | msgid "You don't have permission to view this page" | |
74 |
msgstr "このページを |
|
74 | msgstr "このページを閲覧する権限がありません" | |
75 |
|
75 | |||
76 | #: rhodecode/controllers/error.py:105 |
|
76 | #: rhodecode/controllers/error.py:105 | |
77 | msgid "The resource could not be found" |
|
77 | msgid "The resource could not be found" | |
@@ -285,19 +285,17 b' msgid "An error occurred during deletion' | |||||
285 | msgstr "リポジトリ %s の削除中にエラーが発生しました" |
|
285 | msgstr "リポジトリ %s の削除中にエラーが発生しました" | |
286 |
|
286 | |||
287 | #: rhodecode/controllers/settings.py:185 |
|
287 | #: rhodecode/controllers/settings.py:185 | |
288 | #, fuzzy |
|
|||
289 | msgid "unlocked" |
|
288 | msgid "unlocked" | |
290 |
msgstr " |
|
289 | msgstr "アンロック" | |
291 |
|
290 | |||
292 | #: rhodecode/controllers/settings.py:188 |
|
291 | #: rhodecode/controllers/settings.py:188 | |
293 | #, fuzzy |
|
|||
294 | msgid "locked" |
|
292 | msgid "locked" | |
295 |
msgstr " |
|
293 | msgstr "ロック" | |
296 |
|
294 | |||
297 | #: rhodecode/controllers/settings.py:190 |
|
295 | #: rhodecode/controllers/settings.py:190 | |
298 |
#, |
|
296 | #, python-format | |
299 | msgid "Repository has been %s" |
|
297 | msgid "Repository has been %s" | |
300 | msgstr "" |
|
298 | msgstr "リポジトリは %s されています" | |
301 |
|
299 | |||
302 | #: rhodecode/controllers/settings.py:194 |
|
300 | #: rhodecode/controllers/settings.py:194 | |
303 | #: rhodecode/controllers/admin/repos.py:423 |
|
301 | #: rhodecode/controllers/admin/repos.py:423 | |
@@ -314,14 +312,12 b' msgid "Statistics are disabled for this ' | |||||
314 | msgstr "このリポジトリの統計は無効化されています" |
|
312 | msgstr "このリポジトリの統計は無効化されています" | |
315 |
|
313 | |||
316 | #: rhodecode/controllers/admin/defaults.py:96 |
|
314 | #: rhodecode/controllers/admin/defaults.py:96 | |
317 | #, fuzzy |
|
|||
318 | msgid "Default settings updated successfully" |
|
315 | msgid "Default settings updated successfully" | |
319 |
msgstr " |
|
316 | msgstr "デフォルト設定を更新しました" | |
320 |
|
317 | |||
321 | #: rhodecode/controllers/admin/defaults.py:110 |
|
318 | #: rhodecode/controllers/admin/defaults.py:110 | |
322 | #, fuzzy |
|
|||
323 | msgid "error occurred during update of defaults" |
|
319 | msgid "error occurred during update of defaults" | |
324 |
msgstr " |
|
320 | msgstr "デフォルト設定の更新中にエラーが発生しました" | |
325 |
|
321 | |||
326 | #: rhodecode/controllers/admin/ldap_settings.py:50 |
|
322 | #: rhodecode/controllers/admin/ldap_settings.py:50 | |
327 | msgid "BASE" |
|
323 | msgid "BASE" | |
@@ -473,7 +469,7 b' msgstr "\xe3\x83\xaa\xe3\x83\x9d\xe3\x82\xb8\xe3\x83\x88\xe3\x83\xaa %s \xe3\x82\x92\xe4\xbd\x9c\xe6\x88\x90\xe4\xb8\xad\xe3\x81\xab\xe3\x82\xa8\xe3\x83\xa9\xe3\x83\xbc\xe3\x81\x8c\xe7\x99\xba\xe7\x94\x9f\xe3\x81\x97\xe3\x81\xbe\xe3\x81\x97\xe3\x81\x9f"' | |||||
473 | #: rhodecode/controllers/admin/repos.py:320 |
|
469 | #: rhodecode/controllers/admin/repos.py:320 | |
474 | #, python-format |
|
470 | #, python-format | |
475 | msgid "Cannot delete %s it still contains attached forks" |
|
471 | msgid "Cannot delete %s it still contains attached forks" | |
476 | msgstr "" |
|
472 | msgstr "フォークしたリポジトリが存在するため、 %s は削除できません" | |
477 |
|
473 | |||
478 | #: rhodecode/controllers/admin/repos.py:349 |
|
474 | #: rhodecode/controllers/admin/repos.py:349 | |
479 | msgid "An error occurred during deletion of repository user" |
|
475 | msgid "An error occurred during deletion of repository user" | |
@@ -493,11 +489,11 b' msgstr "\xe3\x82\xad\xe3\x83\xa3\xe3\x83\x83\xe3\x82\xb7\xe3\x83\xa5\xe3\x81\xae\xe7\x84\xa1\xe5\x8a\xb9\xe5\x8c\x96\xe6\x99\x82\xe3\x81\xab\xe3\x82\xa8\xe3\x83\xa9\xe3\x83\xbc\xe3\x81\x8c\xe7\x99\xba\xe7\x94\x9f\xe3\x81\x97\xe3\x81\xbe\xe3\x81\x97\xe3\x81\x9f"' | |||||
493 |
|
489 | |||
494 | #: rhodecode/controllers/admin/repos.py:443 |
|
490 | #: rhodecode/controllers/admin/repos.py:443 | |
495 | msgid "Updated repository visibility in public journal" |
|
491 | msgid "Updated repository visibility in public journal" | |
496 | msgstr "" |
|
492 | msgstr "公開ジャーナルでのリポジトリの可視性を更新しました" | |
497 |
|
493 | |||
498 | #: rhodecode/controllers/admin/repos.py:447 |
|
494 | #: rhodecode/controllers/admin/repos.py:447 | |
499 | msgid "An error occurred during setting this repository in public journal" |
|
495 | msgid "An error occurred during setting this repository in public journal" | |
500 | msgstr "" |
|
496 | msgstr "このリポジトリの公開ジャーナルの設定中にエラーが発生しました" | |
501 |
|
497 | |||
502 | #: rhodecode/controllers/admin/repos.py:452 rhodecode/model/validators.py:300 |
|
498 | #: rhodecode/controllers/admin/repos.py:452 rhodecode/model/validators.py:300 | |
503 | msgid "Token mismatch" |
|
499 | msgid "Token mismatch" | |
@@ -750,7 +746,7 b' msgstr "\xe3\x83\x90\xe3\x82\xa4\xe3\x83\x8a\xe3\x83\xaa\xe3\x83\x95\xe3\x82\xa1\xe3\x82\xa4\xe3\x83\xab"' | |||||
750 |
|
746 | |||
751 | #: rhodecode/lib/diffs.py:90 |
|
747 | #: rhodecode/lib/diffs.py:90 | |
752 | msgid "Changeset was too big and was cut off, use diff menu to display this diff" |
|
748 | msgid "Changeset was too big and was cut off, use diff menu to display this diff" | |
753 | msgstr "" |
|
749 | msgstr "チェンジセットが大きすぎるため省略しました。差分を表示する場合は差分メニューを使用してください" | |
754 |
|
750 | |||
755 | #: rhodecode/lib/diffs.py:100 |
|
751 | #: rhodecode/lib/diffs.py:100 | |
756 | msgid "No changes detected" |
|
752 | msgid "No changes detected" | |
@@ -770,14 +766,14 b' msgid "False"' | |||||
770 | msgstr "False" |
|
766 | msgstr "False" | |
771 |
|
767 | |||
772 | #: rhodecode/lib/helpers.py:530 |
|
768 | #: rhodecode/lib/helpers.py:530 | |
773 |
#, |
|
769 | #, python-format | |
774 | msgid "Deleted branch: %s" |
|
770 | msgid "Deleted branch: %s" | |
775 | msgstr "リポジトリ %s を削除しました" |
|
771 | msgstr "削除されたブランチ: %s" | |
776 |
|
772 | |||
777 | #: rhodecode/lib/helpers.py:533 |
|
773 | #: rhodecode/lib/helpers.py:533 | |
778 |
#, |
|
774 | #, python-format | |
779 | msgid "Created tag: %s" |
|
775 | msgid "Created tag: %s" | |
780 | msgstr "ユーザー %s を作成しました" |
|
776 | msgstr "作成したタグ: %s" | |
781 |
|
777 | |||
782 | #: rhodecode/lib/helpers.py:546 |
|
778 | #: rhodecode/lib/helpers.py:546 | |
783 | msgid "Changeset not found" |
|
779 | msgid "Changeset not found" | |
@@ -794,21 +790,21 b' msgstr "\xe6\xaf\x94\xe8\xbc\x83\xe3\x81\xae\xe8\xa1\xa8\xe7\xa4\xba"' | |||||
794 |
|
790 | |||
795 | #: rhodecode/lib/helpers.py:615 |
|
791 | #: rhodecode/lib/helpers.py:615 | |
796 | msgid "and" |
|
792 | msgid "and" | |
797 | msgstr "" |
|
793 | msgstr "と" | |
798 |
|
794 | |||
799 | #: rhodecode/lib/helpers.py:616 |
|
795 | #: rhodecode/lib/helpers.py:616 | |
800 | #, python-format |
|
796 | #, python-format | |
801 | msgid "%s more" |
|
797 | msgid "%s more" | |
802 | msgstr "" |
|
798 | msgstr "%s 以上" | |
803 |
|
799 | |||
804 | #: rhodecode/lib/helpers.py:617 rhodecode/templates/changelog/changelog.html:51 |
|
800 | #: rhodecode/lib/helpers.py:617 rhodecode/templates/changelog/changelog.html:51 | |
805 | msgid "revisions" |
|
801 | msgid "revisions" | |
806 | msgstr "リビジョン" |
|
802 | msgstr "リビジョン" | |
807 |
|
803 | |||
808 | #: rhodecode/lib/helpers.py:641 |
|
804 | #: rhodecode/lib/helpers.py:641 | |
809 |
#, |
|
805 | #, python-format | |
810 | msgid "fork name %s" |
|
806 | msgid "fork name %s" | |
811 | msgstr "" |
|
807 | msgstr "フォーク名 %s" | |
812 |
|
808 | |||
813 | #: rhodecode/lib/helpers.py:658 |
|
809 | #: rhodecode/lib/helpers.py:658 | |
814 | #: rhodecode/templates/pullrequests/pullrequest_show.html:4 |
|
810 | #: rhodecode/templates/pullrequests/pullrequest_show.html:4 | |
@@ -959,9 +955,9 b' msgid "%s ago"' | |||||
959 | msgstr "%s 前" |
|
955 | msgstr "%s 前" | |
960 |
|
956 | |||
961 | #: rhodecode/lib/utils2.py:428 |
|
957 | #: rhodecode/lib/utils2.py:428 | |
962 |
#, |
|
958 | #, python-format | |
963 | msgid "in %s and %s" |
|
959 | msgid "in %s and %s" | |
964 |
msgstr " |
|
960 | msgstr "" | |
965 |
|
961 | |||
966 | #: rhodecode/lib/utils2.py:431 |
|
962 | #: rhodecode/lib/utils2.py:431 | |
967 | #, python-format |
|
963 | #, python-format | |
@@ -1084,39 +1080,39 b' msgid "Enter %(min)i characters or more"' | |||||
1084 | msgstr "%(min)i 文字以上必要です" |
|
1080 | msgstr "%(min)i 文字以上必要です" | |
1085 |
|
1081 | |||
1086 | #: rhodecode/model/notification.py:220 |
|
1082 | #: rhodecode/model/notification.py:220 | |
1087 |
#, |
|
1083 | #, python-format | |
1088 | msgid "commented on commit at %(when)s" |
|
1084 | msgid "commented on commit at %(when)s" | |
1089 | msgstr "" |
|
1085 | msgstr "コミットにコメント %(when)s" | |
1090 |
|
1086 | |||
1091 | #: rhodecode/model/notification.py:221 |
|
1087 | #: rhodecode/model/notification.py:221 | |
1092 | #, python-format |
|
1088 | #, python-format | |
1093 | msgid "sent message at %(when)s" |
|
1089 | msgid "sent message at %(when)s" | |
1094 | msgstr "" |
|
1090 | msgstr "メッセージを送信 %(when)s" | |
1095 |
|
1091 | |||
1096 | #: rhodecode/model/notification.py:222 |
|
1092 | #: rhodecode/model/notification.py:222 | |
1097 | #, python-format |
|
1093 | #, python-format | |
1098 | msgid "mentioned you at %(when)s" |
|
1094 | msgid "mentioned you at %(when)s" | |
1099 | msgstr "" |
|
1095 | msgstr "Mention %(when)s" | |
1100 |
|
1096 | |||
1101 | #: rhodecode/model/notification.py:223 |
|
1097 | #: rhodecode/model/notification.py:223 | |
1102 | #, python-format |
|
1098 | #, python-format | |
1103 | msgid "registered in RhodeCode at %(when)s" |
|
1099 | msgid "registered in RhodeCode at %(when)s" | |
1104 | msgstr "" |
|
1100 | msgstr "RhodeCodeに登録 %(when)s" | |
1105 |
|
1101 | |||
1106 | #: rhodecode/model/notification.py:224 |
|
1102 | #: rhodecode/model/notification.py:224 | |
1107 |
#, |
|
1103 | #, python-format | |
1108 | msgid "opened new pull request at %(when)s" |
|
1104 | msgid "opened new pull request at %(when)s" | |
1109 | msgstr "" |
|
1105 | msgstr "新しいプルリクエストを作成 %(when)s" | |
1110 |
|
1106 | |||
1111 | #: rhodecode/model/notification.py:225 |
|
1107 | #: rhodecode/model/notification.py:225 | |
1112 |
#, |
|
1108 | #, python-format | |
1113 | msgid "commented on pull request at %(when)s" |
|
1109 | msgid "commented on pull request at %(when)s" | |
1114 | msgstr "" |
|
1110 | msgstr "プルリクエストにコメント %(when)s" | |
1115 |
|
1111 | |||
1116 | #: rhodecode/model/pull_request.py:90 |
|
1112 | #: rhodecode/model/pull_request.py:90 | |
1117 | #, python-format |
|
1113 | #, python-format | |
1118 | msgid "%(user)s wants you to review pull request #%(pr_id)s" |
|
1114 | msgid "%(user)s wants you to review pull request #%(pr_id)s" | |
1119 | msgstr "" |
|
1115 | msgstr "%(user)s がプリリクエスト #%(pr_id)s のレビューを求めています" | |
1120 |
|
1116 | |||
1121 | #: rhodecode/model/scm.py:542 |
|
1117 | #: rhodecode/model/scm.py:542 | |
1122 | msgid "latest tip" |
|
1118 | msgid "latest tip" | |
@@ -1129,11 +1125,11 b' msgstr "\xe6\x96\xb0\xe8\xa6\x8f\xe3\x83\xa6\xe3\x83\xbc\xe3\x82\xb6\xe3\x83\xbc\xe7\x99\xbb\xe9\x8c\xb2"' | |||||
1129 | #: rhodecode/model/user.py:257 rhodecode/model/user.py:281 |
|
1125 | #: rhodecode/model/user.py:257 rhodecode/model/user.py:281 | |
1130 | #: rhodecode/model/user.py:303 |
|
1126 | #: rhodecode/model/user.py:303 | |
1131 | msgid "You can't Edit this user since it's crucial for entire application" |
|
1127 | msgid "You can't Edit this user since it's crucial for entire application" | |
1132 | msgstr "" |
|
1128 | msgstr "アプリケーション全体にとって重要なユーザなため、編集出来ません" | |
1133 |
|
1129 | |||
1134 | #: rhodecode/model/user.py:327 |
|
1130 | #: rhodecode/model/user.py:327 | |
1135 | msgid "You can't remove this user since it's crucial for entire application" |
|
1131 | msgid "You can't remove this user since it's crucial for entire application" | |
1136 | msgstr "" |
|
1132 | msgstr "アプリケーション全体にとって重要なユーザなため、削除できません" | |
1137 |
|
1133 | |||
1138 | #: rhodecode/model/user.py:333 |
|
1134 | #: rhodecode/model/user.py:333 | |
1139 | #, python-format |
|
1135 | #, python-format | |
@@ -1144,7 +1140,7 b' msgstr ""' | |||||
1144 |
|
1140 | |||
1145 | #: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37 |
|
1141 | #: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37 | |
1146 | msgid "Value cannot be an empty list" |
|
1142 | msgid "Value cannot be an empty list" | |
1147 | msgstr "" |
|
1143 | msgstr "空のリストには出来ません" | |
1148 |
|
1144 | |||
1149 | #: rhodecode/model/validators.py:83 |
|
1145 | #: rhodecode/model/validators.py:83 | |
1150 | #, python-format |
|
1146 | #, python-format | |
@@ -1244,16 +1240,15 b' msgstr "\xe7\x84\xa1\xe5\x8a\xb9\xe3\x81\xaa\xe3\x82\xaf\xe3\x83\xad\xe3\x83\xbc\xe3\x83\xb3URI\xe3\x81\xa7\xe3\x81\x99"' | |||||
1244 |
|
1240 | |||
1245 | #: rhodecode/model/validators.py:433 |
|
1241 | #: rhodecode/model/validators.py:433 | |
1246 | msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url" |
|
1242 | msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url" | |
1247 | msgstr "" |
|
1243 | msgstr "無効なクローンURIです。有効な http(s)/svn+http(s) のURIを指定してください" | |
1248 |
|
1244 | |||
1249 | #: rhodecode/model/validators.py:458 |
|
1245 | #: rhodecode/model/validators.py:458 | |
1250 | msgid "Fork have to be the same type as parent" |
|
1246 | msgid "Fork have to be the same type as parent" | |
1251 | msgstr "フォークは親と同じタイプの必要があります" |
|
1247 | msgstr "フォークは親と同じタイプの必要があります" | |
1252 |
|
1248 | |||
1253 | #: rhodecode/model/validators.py:473 |
|
1249 | #: rhodecode/model/validators.py:473 | |
1254 | #, fuzzy |
|
|||
1255 | msgid "You don't have permissions to create repository in this group" |
|
1250 | msgid "You don't have permissions to create repository in this group" | |
1256 |
msgstr "この |
|
1251 | msgstr "このグループでリポジトリを作成する権限がありません" | |
1257 |
|
1252 | |||
1258 | #: rhodecode/model/validators.py:498 |
|
1253 | #: rhodecode/model/validators.py:498 | |
1259 | msgid "This username or users group name is not valid" |
|
1254 | msgid "This username or users group name is not valid" | |
@@ -1617,22 +1612,20 b' msgid "Admin journal"' | |||||
1617 | msgstr "管理者ジャーナル" |
|
1612 | msgstr "管理者ジャーナル" | |
1618 |
|
1613 | |||
1619 | #: rhodecode/templates/admin/admin.html:10 |
|
1614 | #: rhodecode/templates/admin/admin.html:10 | |
1620 | #, fuzzy |
|
|||
1621 | msgid "journal filter..." |
|
1615 | msgid "journal filter..." | |
1622 |
msgstr " |
|
1616 | msgstr "ジャーナルフィルタ..." | |
1623 |
|
1617 | |||
1624 | #: rhodecode/templates/admin/admin.html:12 |
|
1618 | #: rhodecode/templates/admin/admin.html:12 | |
1625 | #: rhodecode/templates/journal/journal.html:11 |
|
1619 | #: rhodecode/templates/journal/journal.html:11 | |
1626 | #, fuzzy |
|
|||
1627 | msgid "filter" |
|
1620 | msgid "filter" | |
1628 |
msgstr "フ |
|
1621 | msgstr "フィルタ" | |
1629 |
|
1622 | |||
1630 | #: rhodecode/templates/admin/admin.html:13 |
|
1623 | #: rhodecode/templates/admin/admin.html:13 | |
1631 | #: rhodecode/templates/journal/journal.html:12 |
|
1624 | #: rhodecode/templates/journal/journal.html:12 | |
1632 | #, python-format |
|
1625 | #, python-format | |
1633 | msgid "%s entry" |
|
1626 | msgid "%s entry" | |
1634 | msgid_plural "%s entries" |
|
1627 | msgid_plural "%s entries" | |
1635 | msgstr[0] "" |
|
1628 | msgstr[0] "%s エントリ" | |
1636 |
|
1629 | |||
1637 | #: rhodecode/templates/admin/admin_log.html:6 |
|
1630 | #: rhodecode/templates/admin/admin_log.html:6 | |
1638 | #: rhodecode/templates/admin/repos/repos.html:74 |
|
1631 | #: rhodecode/templates/admin/repos/repos.html:74 | |
@@ -1668,14 +1661,12 b' msgstr "\xe3\x81\xbe\xe3\x81\xa0\xe3\x82\xa2\xe3\x82\xaf\xe3\x82\xb7\xe3\x83\xa7\xe3\x83\xb3\xe3\x81\x8c\xe3\x81\x82\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x9b\xe3\x82\x93"' | |||||
1668 |
|
1661 | |||
1669 | #: rhodecode/templates/admin/defaults/defaults.html:5 |
|
1662 | #: rhodecode/templates/admin/defaults/defaults.html:5 | |
1670 | #: rhodecode/templates/admin/defaults/defaults.html:25 |
|
1663 | #: rhodecode/templates/admin/defaults/defaults.html:25 | |
1671 | #, fuzzy |
|
|||
1672 | msgid "Repositories defaults" |
|
1664 | msgid "Repositories defaults" | |
1673 |
msgstr "リポジトリ |
|
1665 | msgstr "リポジトリのデフォルト設定" | |
1674 |
|
1666 | |||
1675 | #: rhodecode/templates/admin/defaults/defaults.html:11 |
|
1667 | #: rhodecode/templates/admin/defaults/defaults.html:11 | |
1676 | #, fuzzy |
|
|||
1677 | msgid "Defaults" |
|
1668 | msgid "Defaults" | |
1678 | msgstr "default" |
|
1669 | msgstr "デフォルト設定" | |
1679 |
|
1670 | |||
1680 | #: rhodecode/templates/admin/defaults/defaults.html:35 |
|
1671 | #: rhodecode/templates/admin/defaults/defaults.html:35 | |
1681 | #: rhodecode/templates/admin/repos/repo_add_base.html:38 |
|
1672 | #: rhodecode/templates/admin/repos/repo_add_base.html:38 | |
@@ -1722,7 +1713,7 b' msgstr "\xe3\x83\xad\xe3\x83\x83\xe3\x82\xaf\xe3\x82\x92\xe6\x9c\x89\xe5\x8a\xb9\xe3\x81\xab\xe3\x81\x99\xe3\x82\x8b"' | |||||
1722 | #: rhodecode/templates/admin/defaults/defaults.html:79 |
|
1713 | #: rhodecode/templates/admin/defaults/defaults.html:79 | |
1723 | #: rhodecode/templates/admin/repos/repo_edit.html:116 |
|
1714 | #: rhodecode/templates/admin/repos/repo_edit.html:116 | |
1724 | msgid "Enable lock-by-pulling on repository." |
|
1715 | msgid "Enable lock-by-pulling on repository." | |
1725 | msgstr "" |
|
1716 | msgstr "リポジトリのpullのロックを有効にします" | |
1726 |
|
1717 | |||
1727 | #: rhodecode/templates/admin/defaults/defaults.html:84 |
|
1718 | #: rhodecode/templates/admin/defaults/defaults.html:84 | |
1728 | #: rhodecode/templates/admin/ldap/ldap.html:89 |
|
1719 | #: rhodecode/templates/admin/ldap/ldap.html:89 | |
@@ -2076,20 +2067,19 b' msgstr "\xe3\x83\xaa\xe3\x83\x9d\xe3\x82\xb8\xe3\x83\x88\xe3\x83\xaa\xe3\x81\xae\xe3\x82\xad\xe3\x83\xa3\xe3\x83\x83\xe3\x82\xb7\xe3\x83\xa5\xe3\x82\x92\xe7\x84\xa1\xe5\x8a\xb9\xe5\x8c\x96\xe3\x81\x97\xe3\x81\xa6\xe3\x82\x82\xe3\x82\x88\xe3\x82\x8d\xe3\x81\x97\xe3\x81\x84\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8b\xef\xbc\x9f"' | |||||
2076 | msgid "" |
|
2067 | msgid "" | |
2077 | "Manually invalidate cache for this repository. On first access repository" |
|
2068 | "Manually invalidate cache for this repository. On first access repository" | |
2078 | " will be cached again" |
|
2069 | " will be cached again" | |
2079 | msgstr "" |
|
2070 | msgstr "このリポジトリのキャッシュを手動で無効化します。リポジトリへの初回アクセス時に再びキャッシュされます。" | |
2080 |
|
2071 | |||
2081 | #: rhodecode/templates/admin/repos/repo_edit.html:198 |
|
2072 | #: rhodecode/templates/admin/repos/repo_edit.html:198 | |
2082 | msgid "List of cached values" |
|
2073 | msgid "List of cached values" | |
2083 | msgstr "" |
|
2074 | msgstr "キャッシュしている値の一覧" | |
2084 |
|
2075 | |||
2085 | #: rhodecode/templates/admin/repos/repo_edit.html:201 |
|
2076 | #: rhodecode/templates/admin/repos/repo_edit.html:201 | |
2086 | msgid "Prefix" |
|
2077 | msgid "Prefix" | |
2087 | msgstr "" |
|
2078 | msgstr "プレフィックス" | |
2088 |
|
2079 | |||
2089 | #: rhodecode/templates/admin/repos/repo_edit.html:202 |
|
2080 | #: rhodecode/templates/admin/repos/repo_edit.html:202 | |
2090 | #, fuzzy |
|
|||
2091 | msgid "Key" |
|
2081 | msgid "Key" | |
2092 |
msgstr " |
|
2082 | msgstr "キー" | |
2093 |
|
2083 | |||
2094 | #: rhodecode/templates/admin/repos/repo_edit.html:203 |
|
2084 | #: rhodecode/templates/admin/repos/repo_edit.html:203 | |
2095 | #: rhodecode/templates/admin/users/user_add.html:86 |
|
2085 | #: rhodecode/templates/admin/users/user_add.html:86 | |
@@ -2146,7 +2136,7 b' msgstr "\xe3\x83\xaa\xe3\x83\x9d\xe3\x82\xb8\xe3\x83\x88\xe3\x83\xaa\xe3\x81\xaf\xe3\x83\xad\xe3\x83\x83\xe3\x82\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x9b\xe3\x82\x93"' | |||||
2146 |
|
2136 | |||
2147 | #: rhodecode/templates/admin/repos/repo_edit.html:252 |
|
2137 | #: rhodecode/templates/admin/repos/repo_edit.html:252 | |
2148 | msgid "Force locking on repository. Works only when anonymous access is disabled" |
|
2138 | msgid "Force locking on repository. Works only when anonymous access is disabled" | |
2149 | msgstr "" |
|
2139 | msgstr "リポジトリを強制ロックします。匿名アクセスが無効になっている場合のみ動作します。" | |
2150 |
|
2140 | |||
2151 | #: rhodecode/templates/admin/repos/repo_edit.html:259 |
|
2141 | #: rhodecode/templates/admin/repos/repo_edit.html:259 | |
2152 | msgid "Set as fork of" |
|
2142 | msgid "Set as fork of" | |
@@ -2173,14 +2163,13 b' msgstr "\xe3\x81\x93\xe3\x81\xae\xe3\x83\xaa\xe3\x83\x9d\xe3\x82\xb8\xe3\x83\x88\xe3\x83\xaa\xe3\x82\x92\xe5\x89\x8a\xe9\x99\xa4\xe3\x81\x97\xe3\x81\xbe\xe3\x81\x99\xe3\x81\x8b\xef\xbc\x9f"' | |||||
2173 |
|
2163 | |||
2174 | #: rhodecode/templates/admin/repos/repo_edit.html:282 |
|
2164 | #: rhodecode/templates/admin/repos/repo_edit.html:282 | |
2175 | #: rhodecode/templates/settings/repo_settings.html:119 |
|
2165 | #: rhodecode/templates/settings/repo_settings.html:119 | |
2176 | #, fuzzy |
|
|||
2177 | msgid "" |
|
2166 | msgid "" | |
2178 | "This repository will be renamed in a special way in order to be " |
|
2167 | "This repository will be renamed in a special way in order to be " | |
2179 | "unaccesible for RhodeCode and VCS systems. If you need fully delete it " |
|
2168 | "unaccesible for RhodeCode and VCS systems. If you need fully delete it " | |
2180 | "from file system please do it manually" |
|
2169 | "from file system please do it manually" | |
2181 | msgstr "" |
|
2170 | msgstr "" | |
2182 |
"このリポジトリはRhodeCodeとVCSシステムからアクセス |
|
2171 | "このリポジトリはRhodeCodeとVCSシステムからアクセス出来ないようにするために特別な方法でリネームされます。\n" | |
2183 | "もし、ファイルシステムから完全に削除したい場合、手動で行ってください" |
|
2172 | "完全な削除が必要な場合はファイルシステムから手動で削除してください" | |
2184 |
|
2173 | |||
2185 | #: rhodecode/templates/admin/repos/repo_edit_perms.html:3 |
|
2174 | #: rhodecode/templates/admin/repos/repo_edit_perms.html:3 | |
2186 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3 |
|
2175 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3 | |
@@ -2250,7 +2239,7 b' msgstr "\xe3\x83\xaa\xe3\x83\x9d\xe3\x82\xb8\xe3\x83\x88\xe3\x83\xaa\xe7\xae\xa1\xe7\x90\x86"' | |||||
2250 |
|
2239 | |||
2251 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:73 |
|
2240 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:73 | |
2252 | msgid "apply to children" |
|
2241 | msgid "apply to children" | |
2253 | msgstr "" |
|
2242 | msgstr "子リポジトリにも適用" | |
2254 |
|
2243 | |||
2255 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:74 |
|
2244 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:74 | |
2256 | msgid "" |
|
2245 | msgid "" | |
@@ -2356,10 +2345,10 b' msgid "delete"' | |||||
2356 | msgstr "削除" |
|
2345 | msgstr "削除" | |
2357 |
|
2346 | |||
2358 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:55 |
|
2347 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:55 | |
2359 |
#, |
|
2348 | #, python-format | |
2360 | msgid "Confirm to delete this group: %s with %s repository" |
|
2349 | msgid "Confirm to delete this group: %s with %s repository" | |
2361 | msgid_plural "Confirm to delete this group: %s with %s repositories" |
|
2350 | msgid_plural "Confirm to delete this group: %s with %s repositories" | |
2362 | msgstr[0] "" |
|
2351 | msgstr[0] "このグループを削除してもよろしいですか?: %s %s リポジトリ" | |
2363 |
|
2352 | |||
2364 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:63 |
|
2353 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:63 | |
2365 | msgid "There are no repositories groups yet" |
|
2354 | msgid "There are no repositories groups yet" | |
@@ -2394,11 +2383,11 b' msgstr "\xe3\x83\x95\xe3\x83\x83\xe3\x82\xaf\xe3\x81\xae\xe5\x89\x8a\xe9\x99\xa4\xe3\x81\xab\xe5\xa4\xb1\xe6\x95\x97\xe3\x81\x97\xe3\x81\xbe\xe3\x81\x97\xe3\x81\x9f"' | |||||
2394 |
|
2383 | |||
2395 | #: rhodecode/templates/admin/settings/settings.html:24 |
|
2384 | #: rhodecode/templates/admin/settings/settings.html:24 | |
2396 | msgid "Remap and rescan repositories" |
|
2385 | msgid "Remap and rescan repositories" | |
2397 | msgstr "" |
|
2386 | msgstr "リポジトリの再マッピングと再スキャン" | |
2398 |
|
2387 | |||
2399 | #: rhodecode/templates/admin/settings/settings.html:32 |
|
2388 | #: rhodecode/templates/admin/settings/settings.html:32 | |
2400 | msgid "rescan option" |
|
2389 | msgid "rescan option" | |
2401 | msgstr "" |
|
2390 | msgstr "再スキャンオプション" | |
2402 |
|
2391 | |||
2403 | #: rhodecode/templates/admin/settings/settings.html:38 |
|
2392 | #: rhodecode/templates/admin/settings/settings.html:38 | |
2404 | msgid "" |
|
2393 | msgid "" | |
@@ -2464,13 +2453,12 b' msgid "Visualisation settings"' | |||||
2464 | msgstr "表示の設定" |
|
2453 | msgstr "表示の設定" | |
2465 |
|
2454 | |||
2466 | #: rhodecode/templates/admin/settings/settings.html:127 |
|
2455 | #: rhodecode/templates/admin/settings/settings.html:127 | |
2467 | #, fuzzy |
|
|||
2468 | msgid "General" |
|
2456 | msgid "General" | |
2469 |
msgstr " |
|
2457 | msgstr "一般" | |
2470 |
|
2458 | |||
2471 | #: rhodecode/templates/admin/settings/settings.html:132 |
|
2459 | #: rhodecode/templates/admin/settings/settings.html:132 | |
2472 | msgid "Use lightweight dashboard" |
|
2460 | msgid "Use lightweight dashboard" | |
2473 | msgstr "" |
|
2461 | msgstr "軽量ダッシュボードを使用" | |
2474 |
|
2462 | |||
2475 | #: rhodecode/templates/admin/settings/settings.html:139 |
|
2463 | #: rhodecode/templates/admin/settings/settings.html:139 | |
2476 | msgid "Icons" |
|
2464 | msgid "Icons" | |
@@ -2508,7 +2496,7 b' msgstr "VCS\xe3\x81\xae\xe6\x93\x8d\xe4\xbd\x9c\xe3\x81\xabSSL\xe3\x82\x92\xe5\xbf\x85\xe9\xa0\x88\xe3\x81\xa8\xe3\x81\x99\xe3\x82\x8b"' | |||||
2508 | msgid "" |
|
2496 | msgid "" | |
2509 | "RhodeCode will require SSL for pushing or pulling. If SSL is missing it " |
|
2497 | "RhodeCode will require SSL for pushing or pulling. If SSL is missing it " | |
2510 | "will return HTTP Error 406: Not Acceptable" |
|
2498 | "will return HTTP Error 406: Not Acceptable" | |
2511 | msgstr "" |
|
2499 | msgstr "RhodeCodeはPushとPullにSSLを要求します。もしSSLでない場合、HTTP Error 406: Not Acceptalbeを返します" | |
2512 |
|
2500 | |||
2513 | #: rhodecode/templates/admin/settings/settings.html:209 |
|
2501 | #: rhodecode/templates/admin/settings/settings.html:209 | |
2514 | msgid "Hooks" |
|
2502 | msgid "Hooks" | |
@@ -2561,12 +2549,15 b' msgid ""' | |||||
2561 | "This a crucial application setting. If you are really sure you need to " |
|
2549 | "This a crucial application setting. If you are really sure you need to " | |
2562 | "change this, you must restart application in order to make this setting " |
|
2550 | "change this, you must restart application in order to make this setting " | |
2563 | "take effect. Click this label to unlock." |
|
2551 | "take effect. Click this label to unlock." | |
2564 | msgstr "これはアプリケーションの重要な設定です。本当に変更が必要でしょうか。もし、変更した場合、変更を反映さ競るためにアプリケーションを再起動する必要があります。変更可能にするにはこのラベルをクリックして下さい" |
|
2552 | msgstr "" | |
|
2553 | "これはアプリケーションの重要な設定です。本当に変更が必要でしょうか。" | |||
|
2554 | "もし、変更した場合、変更を反映さ競るためにアプリケーションを再起動する必要があります。" | |||
|
2555 | "アンロックにするにはこのラベルをクリックして下さい" | |||
2565 |
|
2556 | |||
2566 | #: rhodecode/templates/admin/settings/settings.html:262 |
|
2557 | #: rhodecode/templates/admin/settings/settings.html:262 | |
2567 | #: rhodecode/templates/base/base.html:221 |
|
2558 | #: rhodecode/templates/base/base.html:221 | |
2568 | msgid "unlock" |
|
2559 | msgid "unlock" | |
2569 |
msgstr " |
|
2560 | msgstr "アンロック" | |
2570 |
|
2561 | |||
2571 | #: rhodecode/templates/admin/settings/settings.html:263 |
|
2562 | #: rhodecode/templates/admin/settings/settings.html:263 | |
2572 | msgid "" |
|
2563 | msgid "" | |
@@ -2869,19 +2860,16 b' msgid "Group members"' | |||||
2869 | msgstr "グループメンバー" |
|
2860 | msgstr "グループメンバー" | |
2870 |
|
2861 | |||
2871 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:163 |
|
2862 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:163 | |
2872 | #, fuzzy |
|
|||
2873 | msgid "No members yet" |
|
2863 | msgid "No members yet" | |
2874 | msgstr "メンバー" |
|
2864 | msgstr "まだメンバーがいません" | |
2875 |
|
2865 | |||
2876 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:171 |
|
2866 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:171 | |
2877 | #, fuzzy |
|
|||
2878 | msgid "Permissions defined for this group" |
|
2867 | msgid "Permissions defined for this group" | |
2879 |
msgstr " |
|
2868 | msgstr "このリポジトリの権限設定" | |
2880 |
|
2869 | |||
2881 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:178 |
|
2870 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:178 | |
2882 | #, fuzzy |
|
|||
2883 | msgid "No permissions set yet" |
|
2871 | msgid "No permissions set yet" | |
2884 |
msgstr " |
|
2872 | msgstr "まだ権限設定がありません" | |
2885 |
|
2873 | |||
2886 | #: rhodecode/templates/admin/users_groups/users_groups.html:5 |
|
2874 | #: rhodecode/templates/admin/users_groups/users_groups.html:5 | |
2887 | msgid "Users groups administration" |
|
2875 | msgid "Users groups administration" | |
@@ -2992,9 +2980,8 b' msgstr "\xe3\x82\xaa\xe3\x83\x97\xe3\x82\xb7\xe3\x83\xa7\xe3\x83\xb3"' | |||||
2992 |
|
2980 | |||
2993 | #: rhodecode/templates/base/base.html:204 |
|
2981 | #: rhodecode/templates/base/base.html:204 | |
2994 | #: rhodecode/templates/base/base.html:206 |
|
2982 | #: rhodecode/templates/base/base.html:206 | |
2995 | #, fuzzy |
|
|||
2996 | msgid "repository settings" |
|
2983 | msgid "repository settings" | |
2997 |
msgstr "リポジトリ |
|
2984 | msgstr "リポジトリ設定" | |
2998 |
|
2985 | |||
2999 | #: rhodecode/templates/base/base.html:210 |
|
2986 | #: rhodecode/templates/base/base.html:210 | |
3000 | #: rhodecode/templates/data_table/_dt_elements.html:80 |
|
2987 | #: rhodecode/templates/data_table/_dt_elements.html:80 | |
@@ -3017,9 +3004,8 b' msgid "search"' | |||||
3017 | msgstr "検索" |
|
3004 | msgstr "検索" | |
3018 |
|
3005 | |||
3019 | #: rhodecode/templates/base/base.html:223 |
|
3006 | #: rhodecode/templates/base/base.html:223 | |
3020 | #, fuzzy |
|
|||
3021 | msgid "lock" |
|
3007 | msgid "lock" | |
3022 |
msgstr " |
|
3008 | msgstr "ロック" | |
3023 |
|
3009 | |||
3024 | #: rhodecode/templates/base/base.html:234 |
|
3010 | #: rhodecode/templates/base/base.html:234 | |
3025 | msgid "repositories groups" |
|
3011 | msgid "repositories groups" | |
@@ -3034,9 +3020,8 b' msgid "permissions"' | |||||
3034 | msgstr "権限" |
|
3020 | msgstr "権限" | |
3035 |
|
3021 | |||
3036 | #: rhodecode/templates/base/base.html:239 |
|
3022 | #: rhodecode/templates/base/base.html:239 | |
3037 | #, fuzzy |
|
|||
3038 | msgid "defaults" |
|
3023 | msgid "defaults" | |
3039 | msgstr "default" |
|
3024 | msgstr "デフォルト設定" | |
3040 |
|
3025 | |||
3041 | #: rhodecode/templates/base/base.html:240 |
|
3026 | #: rhodecode/templates/base/base.html:240 | |
3042 | msgid "settings" |
|
3027 | msgid "settings" | |
@@ -3087,13 +3072,12 b' msgid "no matching files"' | |||||
3087 | msgstr "マッチするファイルはありません" |
|
3072 | msgstr "マッチするファイルはありません" | |
3088 |
|
3073 | |||
3089 | #: rhodecode/templates/base/root.html:51 |
|
3074 | #: rhodecode/templates/base/root.html:51 | |
3090 | #, fuzzy |
|
|||
3091 | msgid "Open new pull request for selected changesets" |
|
3075 | msgid "Open new pull request for selected changesets" | |
3092 | msgstr "新しいプルリクエストを作成" |
|
3076 | msgstr "選択したチェンジセットから新しいプルリクエストを作成" | |
3093 |
|
3077 | |||
3094 | #: rhodecode/templates/base/root.html:52 |
|
3078 | #: rhodecode/templates/base/root.html:52 | |
3095 | msgid "Show selected changes __S -> __E" |
|
3079 | msgid "Show selected changes __S -> __E" | |
3096 | msgstr "" |
|
3080 | msgstr "選択した変更 __S -> __E を表示" | |
3097 |
|
3081 | |||
3098 | #: rhodecode/templates/base/root.html:53 |
|
3082 | #: rhodecode/templates/base/root.html:53 | |
3099 | msgid "Selection link" |
|
3083 | msgid "Selection link" | |
@@ -3143,9 +3127,8 b' msgid_plural "showing %d out of %d revis' | |||||
3143 | msgstr[0] "" |
|
3127 | msgstr[0] "" | |
3144 |
|
3128 | |||
3145 | #: rhodecode/templates/changelog/changelog.html:37 |
|
3129 | #: rhodecode/templates/changelog/changelog.html:37 | |
3146 | #, fuzzy |
|
|||
3147 | msgid "Clear selection" |
|
3130 | msgid "Clear selection" | |
3148 |
msgstr " |
|
3131 | msgstr "選択を解除" | |
3149 |
|
3132 | |||
3150 | #: rhodecode/templates/changelog/changelog.html:40 |
|
3133 | #: rhodecode/templates/changelog/changelog.html:40 | |
3151 | #: rhodecode/templates/forks/forks_data.html:19 |
|
3134 | #: rhodecode/templates/forks/forks_data.html:19 | |
@@ -3154,9 +3137,8 b' msgid "compare fork with %s"' | |||||
3154 | msgstr "%s とフォークを比較" |
|
3137 | msgstr "%s とフォークを比較" | |
3155 |
|
3138 | |||
3156 | #: rhodecode/templates/changelog/changelog.html:40 |
|
3139 | #: rhodecode/templates/changelog/changelog.html:40 | |
3157 | #, fuzzy |
|
|||
3158 | msgid "Compare fork with parent" |
|
3140 | msgid "Compare fork with parent" | |
3159 |
msgstr " |
|
3141 | msgstr "フォークを比較" | |
3160 |
|
3142 | |||
3161 | #: rhodecode/templates/changelog/changelog.html:49 |
|
3143 | #: rhodecode/templates/changelog/changelog.html:49 | |
3162 | msgid "Show" |
|
3144 | msgid "Show" | |
@@ -3259,7 +3241,7 b' msgstr "\xe3\x83\x81\xe3\x82\xa7\xe3\x83\xb3\xe3\x82\xb8\xe3\x82\xbb\xe3\x83\x83\xe3\x83\x88"' | |||||
3259 |
|
3241 | |||
3260 | #: rhodecode/templates/changeset/changeset.html:52 |
|
3242 | #: rhodecode/templates/changeset/changeset.html:52 | |
3261 | msgid "No children" |
|
3243 | msgid "No children" | |
3262 | msgstr "" |
|
3244 | msgstr "子リビジョンはありません" | |
3263 |
|
3245 | |||
3264 | #: rhodecode/templates/changeset/changeset.html:70 |
|
3246 | #: rhodecode/templates/changeset/changeset.html:70 | |
3265 | #: rhodecode/templates/changeset/diff_block.html:20 |
|
3247 | #: rhodecode/templates/changeset/diff_block.html:20 | |
@@ -3267,9 +3249,8 b' msgid "raw diff"' | |||||
3267 | msgstr "差分を表示" |
|
3249 | msgstr "差分を表示" | |
3268 |
|
3250 | |||
3269 | #: rhodecode/templates/changeset/changeset.html:71 |
|
3251 | #: rhodecode/templates/changeset/changeset.html:71 | |
3270 | #, fuzzy |
|
|||
3271 | msgid "patch diff" |
|
3252 | msgid "patch diff" | |
3272 | msgstr "差分を表示" |
|
3253 | msgstr "パッチとして差分を表示" | |
3273 |
|
3254 | |||
3274 | #: rhodecode/templates/changeset/changeset.html:72 |
|
3255 | #: rhodecode/templates/changeset/changeset.html:72 | |
3275 | #: rhodecode/templates/changeset/diff_block.html:21 |
|
3256 | #: rhodecode/templates/changeset/diff_block.html:21 | |
@@ -3293,18 +3274,18 b' msgstr[0] "(%d \xe3\x82\xa4\xe3\x83\xb3\xe3\x83\xa9\xe3\x82\xa4\xe3\x83\xb3)"' | |||||
3293 | #: rhodecode/templates/changeset/changeset.html:122 |
|
3274 | #: rhodecode/templates/changeset/changeset.html:122 | |
3294 | #: rhodecode/templates/compare/compare_diff.html:44 |
|
3275 | #: rhodecode/templates/compare/compare_diff.html:44 | |
3295 | #: rhodecode/templates/pullrequests/pullrequest_show.html:76 |
|
3276 | #: rhodecode/templates/pullrequests/pullrequest_show.html:76 | |
3296 |
#, |
|
3277 | #, python-format | |
3297 | msgid "%s file changed" |
|
3278 | msgid "%s file changed" | |
3298 | msgid_plural "%s files changed" |
|
3279 | msgid_plural "%s files changed" | |
3299 | msgstr[0] "" |
|
3280 | msgstr[0] "%s ファイルに影響" | |
3300 |
|
3281 | |||
3301 | #: rhodecode/templates/changeset/changeset.html:124 |
|
3282 | #: rhodecode/templates/changeset/changeset.html:124 | |
3302 | #: rhodecode/templates/compare/compare_diff.html:46 |
|
3283 | #: rhodecode/templates/compare/compare_diff.html:46 | |
3303 | #: rhodecode/templates/pullrequests/pullrequest_show.html:78 |
|
3284 | #: rhodecode/templates/pullrequests/pullrequest_show.html:78 | |
3304 |
#, |
|
3285 | #, python-format | |
3305 | msgid "%s file changed with %s insertions and %s deletions" |
|
3286 | msgid "%s file changed with %s insertions and %s deletions" | |
3306 | msgid_plural "%s files changed with %s insertions and %s deletions" |
|
3287 | msgid_plural "%s files changed with %s insertions and %s deletions" | |
3307 |
msgstr[0] "%s ファイルに影響。 %s 個の追加と %s 個の削除 |
|
3288 | msgstr[0] "%s ファイルに影響。 %s 個の追加と %s 個の削除" | |
3308 |
|
3289 | |||
3309 | #: rhodecode/templates/changeset/changeset_file_comment.html:42 |
|
3290 | #: rhodecode/templates/changeset/changeset_file_comment.html:42 | |
3310 | msgid "Submitting..." |
|
3291 | msgid "Submitting..." | |
@@ -3349,7 +3330,7 b' msgstr "\xe3\x82\xb3\xe3\x83\xa1\xe3\x83\xb3\xe3\x83\x88\xe3\x82\x92\xe6\xae\x8b\xe3\x81\x99"' | |||||
3349 |
|
3330 | |||
3350 | #: rhodecode/templates/changeset/changeset_file_comment.html:125 |
|
3331 | #: rhodecode/templates/changeset/changeset_file_comment.html:125 | |
3351 | msgid "Check this to change current status of code-review for this changeset" |
|
3332 | msgid "Check this to change current status of code-review for this changeset" | |
3352 | msgstr "" |
|
3333 | msgstr "チェックするとチェンジセットの現在のコードレビューステータスを変更出来ます" | |
3353 |
|
3334 | |||
3354 | #: rhodecode/templates/changeset/changeset_file_comment.html:125 |
|
3335 | #: rhodecode/templates/changeset/changeset_file_comment.html:125 | |
3355 | msgid "change status" |
|
3336 | msgid "change status" | |
@@ -3370,9 +3351,8 b' msgid "Compare View"' | |||||
3370 | msgstr "比較ビュー" |
|
3351 | msgstr "比較ビュー" | |
3371 |
|
3352 | |||
3372 | #: rhodecode/templates/changeset/changeset_range.html:29 |
|
3353 | #: rhodecode/templates/changeset/changeset_range.html:29 | |
3373 | #, fuzzy |
|
|||
3374 | msgid "Show combined compare" |
|
3354 | msgid "Show combined compare" | |
3375 | msgstr "インラインコメントを表示" |
|
3355 | msgstr "結合した比較ビューを表示" | |
3376 |
|
3356 | |||
3377 | #: rhodecode/templates/changeset/changeset_range.html:54 |
|
3357 | #: rhodecode/templates/changeset/changeset_range.html:54 | |
3378 | msgid "Files affected" |
|
3358 | msgid "Files affected" | |
@@ -3380,7 +3360,7 b' msgstr "\xe5\xbd\xb1\xe9\x9f\xbf\xe3\x81\xae\xe3\x81\x82\xe3\x82\x8b\xe3\x83\x95\xe3\x82\xa1\xe3\x82\xa4\xe3\x83\xab"' | |||||
3380 |
|
3360 | |||
3381 | #: rhodecode/templates/changeset/diff_block.html:19 |
|
3361 | #: rhodecode/templates/changeset/diff_block.html:19 | |
3382 | msgid "show full diff for this file" |
|
3362 | msgid "show full diff for this file" | |
3383 | msgstr "" |
|
3363 | msgstr "このファイルの全差分を表示" | |
3384 |
|
3364 | |||
3385 | #: rhodecode/templates/changeset/diff_block.html:27 |
|
3365 | #: rhodecode/templates/changeset/diff_block.html:27 | |
3386 | msgid "show inline comments" |
|
3366 | msgid "show inline comments" | |
@@ -3392,16 +3372,15 b' msgstr "\xe3\x83\x81\xe3\x82\xa7\xe3\x83\xb3\xe3\x82\xb8\xe3\x82\xbb\xe3\x83\x83\xe3\x83\x88\xe3\x81\xaf\xe3\x81\x82\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x9b\xe3\x82\x93"' | |||||
3392 |
|
3372 | |||
3393 | #: rhodecode/templates/compare/compare_diff.html:37 |
|
3373 | #: rhodecode/templates/compare/compare_diff.html:37 | |
3394 | #: rhodecode/templates/pullrequests/pullrequest_show.html:69 |
|
3374 | #: rhodecode/templates/pullrequests/pullrequest_show.html:69 | |
3395 |
#, |
|
3375 | #, python-format | |
3396 | msgid "Showing %s commit" |
|
3376 | msgid "Showing %s commit" | |
3397 | msgid_plural "Showing %s commits" |
|
3377 | msgid_plural "Showing %s commits" | |
3398 | msgstr[0] "" |
|
3378 | msgstr[0] "%s コミットを表示" | |
3399 |
|
3379 | |||
3400 | #: rhodecode/templates/compare/compare_diff.html:52 |
|
3380 | #: rhodecode/templates/compare/compare_diff.html:52 | |
3401 | #: rhodecode/templates/pullrequests/pullrequest_show.html:84 |
|
3381 | #: rhodecode/templates/pullrequests/pullrequest_show.html:84 | |
3402 | #, fuzzy |
|
|||
3403 | msgid "No files" |
|
3382 | msgid "No files" | |
3404 | msgstr "ファイル" |
|
3383 | msgstr "ファイルはありません" | |
3405 |
|
3384 | |||
3406 | #: rhodecode/templates/data_table/_dt_elements.html:39 |
|
3385 | #: rhodecode/templates/data_table/_dt_elements.html:39 | |
3407 | #: rhodecode/templates/data_table/_dt_elements.html:41 |
|
3386 | #: rhodecode/templates/data_table/_dt_elements.html:41 | |
@@ -3455,40 +3434,37 b' msgid "Confirm to delete this user: %s"' | |||||
3455 | msgstr "このユーザーを本当に削除してよろしいですか?: %s" |
|
3434 | msgstr "このユーザーを本当に削除してよろしいですか?: %s" | |
3456 |
|
3435 | |||
3457 | #: rhodecode/templates/email_templates/changeset_comment.html:10 |
|
3436 | #: rhodecode/templates/email_templates/changeset_comment.html:10 | |
3458 | #, fuzzy |
|
|||
3459 | msgid "New status$" |
|
3437 | msgid "New status$" | |
3460 |
msgstr " |
|
3438 | msgstr "新しいステータス$" | |
3461 |
|
3439 | |||
3462 | #: rhodecode/templates/email_templates/main.html:8 |
|
3440 | #: rhodecode/templates/email_templates/main.html:8 | |
3463 | #, fuzzy |
|
|||
3464 | msgid "This is a notification from RhodeCode." |
|
3441 | msgid "This is a notification from RhodeCode." | |
3465 |
msgstr "RhodeCodeからの通知 |
|
3442 | msgstr "RhodeCodeからの通知です" | |
3466 |
|
3443 | |||
3467 | #: rhodecode/templates/email_templates/password_reset.html:4 |
|
3444 | #: rhodecode/templates/email_templates/password_reset.html:4 | |
3468 | msgid "Hello" |
|
3445 | msgid "Hello" | |
3469 | msgstr "" |
|
3446 | msgstr "こんにちは" | |
3470 |
|
3447 | |||
3471 | #: rhodecode/templates/email_templates/password_reset.html:6 |
|
3448 | #: rhodecode/templates/email_templates/password_reset.html:6 | |
3472 | msgid "We received a request to create a new password for your account." |
|
3449 | msgid "We received a request to create a new password for your account." | |
3473 | msgstr "" |
|
3450 | msgstr "あなたのアカウントの新しいパスワードの生成リクエストを受け取りました。" | |
3474 |
|
3451 | |||
3475 | #: rhodecode/templates/email_templates/password_reset.html:8 |
|
3452 | #: rhodecode/templates/email_templates/password_reset.html:8 | |
3476 | msgid "You can generate it by clicking following URL" |
|
3453 | msgid "You can generate it by clicking following URL" | |
3477 | msgstr "" |
|
3454 | msgstr "下のURLをクリックすることで再生成が行えます。" | |
3478 |
|
3455 | |||
3479 | #: rhodecode/templates/email_templates/password_reset.html:12 |
|
3456 | #: rhodecode/templates/email_templates/password_reset.html:12 | |
3480 | msgid "If you didn't request new password please ignore this email." |
|
3457 | msgid "If you didn't request new password please ignore this email." | |
3481 | msgstr "" |
|
3458 | msgstr "新しいパスワードのリクエストをしていない場合は、このメールを無視して下さい。" | |
3482 |
|
3459 | |||
3483 | #: rhodecode/templates/email_templates/pull_request.html:4 |
|
3460 | #: rhodecode/templates/email_templates/pull_request.html:4 | |
3484 | #, python-format |
|
3461 | #, python-format | |
3485 | msgid "" |
|
3462 | msgid "" | |
3486 | "User %s opened pull request for repository %s and wants you to review " |
|
3463 | "User %s opened pull request for repository %s and wants you to review " | |
3487 | "changes." |
|
3464 | "changes." | |
3488 | msgstr "" |
|
3465 | msgstr "ユーザ %s がリポジトリ %s で新しいプルリクエストを作成しました。変更をレビューしてください。" | |
3489 |
|
3466 | |||
3490 | #: rhodecode/templates/email_templates/pull_request.html:5 |
|
3467 | #: rhodecode/templates/email_templates/pull_request.html:5 | |
3491 | #, fuzzy |
|
|||
3492 | msgid "title" |
|
3468 | msgid "title" | |
3493 | msgstr "タイトル" |
|
3469 | msgstr "タイトル" | |
3494 |
|
3470 | |||
@@ -3499,35 +3475,32 b' msgstr "\xe8\xaa\xac\xe6\x98\x8e"' | |||||
3499 |
|
3475 | |||
3500 | #: rhodecode/templates/email_templates/pull_request.html:11 |
|
3476 | #: rhodecode/templates/email_templates/pull_request.html:11 | |
3501 | msgid "revisions for reviewing" |
|
3477 | msgid "revisions for reviewing" | |
3502 | msgstr "" |
|
3478 | msgstr "レビュー対象のリビジョン" | |
3503 |
|
3479 | |||
3504 | #: rhodecode/templates/email_templates/pull_request.html:18 |
|
3480 | #: rhodecode/templates/email_templates/pull_request.html:18 | |
3505 | #, fuzzy |
|
|||
3506 | msgid "View this pull request here" |
|
3481 | msgid "View this pull request here" | |
3507 |
msgstr "このプルリクエスト |
|
3482 | msgstr "このプルリクエストを閲覧する" | |
3508 |
|
3483 | |||
3509 | #: rhodecode/templates/email_templates/pull_request_comment.html:4 |
|
3484 | #: rhodecode/templates/email_templates/pull_request_comment.html:4 | |
3510 |
#, |
|
3485 | #, python-format | |
3511 | msgid "User %s commented on pull request #%s for repository %s" |
|
3486 | msgid "User %s commented on pull request #%s for repository %s" | |
3512 | msgstr "" |
|
3487 | msgstr "ユーザ %s がプルリクエスト #%s (リポジトリ %s) にコメントしました。" | |
3513 |
|
3488 | |||
3514 | #: rhodecode/templates/email_templates/pull_request_comment.html:10 |
|
3489 | #: rhodecode/templates/email_templates/pull_request_comment.html:10 | |
3515 | #, fuzzy |
|
|||
3516 | msgid "New status" |
|
3490 | msgid "New status" | |
3517 |
msgstr "ステータス |
|
3491 | msgstr "新しいステータス" | |
3518 |
|
3492 | |||
3519 | #: rhodecode/templates/email_templates/pull_request_comment.html:14 |
|
3493 | #: rhodecode/templates/email_templates/pull_request_comment.html:14 | |
3520 | msgid "View this comment here" |
|
3494 | msgid "View this comment here" | |
3521 | msgstr "" |
|
3495 | msgstr "このコメントを閲覧する" | |
3522 |
|
3496 | |||
3523 | #: rhodecode/templates/email_templates/registration.html:4 |
|
3497 | #: rhodecode/templates/email_templates/registration.html:4 | |
3524 | #, fuzzy |
|
|||
3525 | msgid "A new user have registered in RhodeCode" |
|
3498 | msgid "A new user have registered in RhodeCode" | |
3526 |
msgstr " |
|
3499 | msgstr "新しいユーザがRhodeCodeへ登録しました" | |
3527 |
|
3500 | |||
3528 | #: rhodecode/templates/email_templates/registration.html:9 |
|
3501 | #: rhodecode/templates/email_templates/registration.html:9 | |
3529 | msgid "View this user here" |
|
3502 | msgid "View this user here" | |
3530 | msgstr "" |
|
3503 | msgstr "このユーザを閲覧する" | |
3531 |
|
3504 | |||
3532 | #: rhodecode/templates/errors/error_document.html:46 |
|
3505 | #: rhodecode/templates/errors/error_document.html:46 | |
3533 | #, python-format |
|
3506 | #, python-format | |
@@ -3609,7 +3582,7 b' msgstr "\xe5\xa4\x89\xe6\x9b\xb4\xe3\x82\x92\xe3\x82\xb3\xe3\x83\x9f\xe3\x83\x83\xe3\x83\x88"' | |||||
3609 |
|
3582 | |||
3610 | #: rhodecode/templates/files/files_browser.html:13 |
|
3583 | #: rhodecode/templates/files/files_browser.html:13 | |
3611 | msgid "view" |
|
3584 | msgid "view" | |
3612 |
msgstr " |
|
3585 | msgstr "閲覧" | |
3613 |
|
3586 | |||
3614 | #: rhodecode/templates/files/files_browser.html:14 |
|
3587 | #: rhodecode/templates/files/files_browser.html:14 | |
3615 | msgid "previous revision" |
|
3588 | msgid "previous revision" | |
@@ -3697,9 +3670,8 b' msgid "show at revision"' | |||||
3697 | msgstr "このリビジョンを見る" |
|
3670 | msgstr "このリビジョンを見る" | |
3698 |
|
3671 | |||
3699 | #: rhodecode/templates/files/files_history_box.html:11 |
|
3672 | #: rhodecode/templates/files/files_history_box.html:11 | |
3700 | #, fuzzy |
|
|||
3701 | msgid "show full history" |
|
3673 | msgid "show full history" | |
3702 | msgstr "ファイル一覧を読み込み中..." |
|
3674 | msgstr "すべての履歴を表示" | |
3703 |
|
3675 | |||
3704 | #: rhodecode/templates/files/files_history_box.html:16 |
|
3676 | #: rhodecode/templates/files/files_history_box.html:16 | |
3705 | #, python-format |
|
3677 | #, python-format | |
@@ -3708,9 +3680,8 b' msgid_plural "%s authors"' | |||||
3708 | msgstr[0] "%s 作成者" |
|
3680 | msgstr[0] "%s 作成者" | |
3709 |
|
3681 | |||
3710 | #: rhodecode/templates/files/files_source.html:6 |
|
3682 | #: rhodecode/templates/files/files_source.html:6 | |
3711 | #, fuzzy |
|
|||
3712 | msgid "Load file history" |
|
3683 | msgid "Load file history" | |
3713 |
msgstr "ファイル |
|
3684 | msgstr "ファイルの履歴を読み込む" | |
3714 |
|
3685 | |||
3715 | #: rhodecode/templates/files/files_source.html:21 |
|
3686 | #: rhodecode/templates/files/files_source.html:21 | |
3716 | msgid "show source" |
|
3687 | msgid "show source" | |
@@ -3909,7 +3880,7 b' msgstr "%s \xe3\x81\xab\xe3\x82\xaf\xe3\x83\xad\xe3\x83\xbc\xe3\x82\xba"' | |||||
3909 | #: rhodecode/templates/pullrequests/pullrequest_show.html:23 |
|
3880 | #: rhodecode/templates/pullrequests/pullrequest_show.html:23 | |
3910 | #, python-format |
|
3881 | #, python-format | |
3911 | msgid "with status %s" |
|
3882 | msgid "with status %s" | |
3912 | msgstr "" |
|
3883 | msgstr "ステータス: %s" | |
3913 |
|
3884 | |||
3914 | #: rhodecode/templates/pullrequests/pullrequest_show.html:31 |
|
3885 | #: rhodecode/templates/pullrequests/pullrequest_show.html:31 | |
3915 | msgid "Status" |
|
3886 | msgid "Status" | |
@@ -3930,9 +3901,8 b' msgid_plural "%d reviewers"' | |||||
3930 | msgstr[0] "%d レビュアー" |
|
3901 | msgstr[0] "%d レビュアー" | |
3931 |
|
3902 | |||
3932 | #: rhodecode/templates/pullrequests/pullrequest_show.html:50 |
|
3903 | #: rhodecode/templates/pullrequests/pullrequest_show.html:50 | |
3933 | #, fuzzy |
|
|||
3934 | msgid "pull request was reviewed by all reviewers" |
|
3904 | msgid "pull request was reviewed by all reviewers" | |
3935 | msgstr "プルリクエストレビュアー" |
|
3905 | msgstr "プルリクエストはすべてのレビュアーにレビューされました" | |
3936 |
|
3906 | |||
3937 | #: rhodecode/templates/pullrequests/pullrequest_show.html:58 |
|
3907 | #: rhodecode/templates/pullrequests/pullrequest_show.html:58 | |
3938 | msgid "Created on" |
|
3908 | msgid "Created on" | |
@@ -3943,9 +3913,8 b' msgid "Compare view"' | |||||
3943 | msgstr "比較ビュー" |
|
3913 | msgstr "比較ビュー" | |
3944 |
|
3914 | |||
3945 | #: rhodecode/templates/pullrequests/pullrequest_show.html:112 |
|
3915 | #: rhodecode/templates/pullrequests/pullrequest_show.html:112 | |
3946 | #, fuzzy |
|
|||
3947 | msgid "reviewer" |
|
3916 | msgid "reviewer" | |
3948 |
msgstr " |
|
3917 | msgstr "レビュアー" | |
3949 |
|
3918 | |||
3950 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4 |
|
3919 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4 | |
3951 | msgid "all pull requests" |
|
3920 | msgid "all pull requests" | |
@@ -4012,12 +3981,10 b' msgid "%s Settings"' | |||||
4012 | msgstr "%s 設定" |
|
3981 | msgstr "%s 設定" | |
4013 |
|
3982 | |||
4014 | #: rhodecode/templates/settings/repo_settings.html:102 |
|
3983 | #: rhodecode/templates/settings/repo_settings.html:102 | |
4015 | #, fuzzy |
|
|||
4016 | msgid "Delete repository" |
|
3984 | msgid "Delete repository" | |
4017 |
msgstr "リポジトリを |
|
3985 | msgstr "リポジトリを削除" | |
4018 |
|
3986 | |||
4019 | #: rhodecode/templates/settings/repo_settings.html:109 |
|
3987 | #: rhodecode/templates/settings/repo_settings.html:109 | |
4020 | #, fuzzy |
|
|||
4021 | msgid "Remove repo" |
|
3988 | msgid "Remove repo" | |
4022 | msgstr "削除" |
|
3989 | msgstr "削除" | |
4023 |
|
3990 | |||
@@ -4080,19 +4047,18 b' msgid "ATOM"' | |||||
4080 | msgstr "ATOM" |
|
4047 | msgstr "ATOM" | |
4081 |
|
4048 | |||
4082 | #: rhodecode/templates/summary/summary.html:70 |
|
4049 | #: rhodecode/templates/summary/summary.html:70 | |
4083 |
#, |
|
4050 | #, python-format | |
4084 | msgid "Repository locked by %s" |
|
4051 | msgid "Repository locked by %s" | |
4085 | msgstr "" |
|
4052 | msgstr "リポジトリは %s によってロックされました" | |
4086 |
|
4053 | |||
4087 | #: rhodecode/templates/summary/summary.html:72 |
|
4054 | #: rhodecode/templates/summary/summary.html:72 | |
4088 | #, fuzzy |
|
|||
4089 | msgid "Repository unlocked" |
|
4055 | msgid "Repository unlocked" | |
4090 | msgstr "リポジトリはロックされていません" |
|
4056 | msgstr "リポジトリはロックされていません" | |
4091 |
|
4057 | |||
4092 | #: rhodecode/templates/summary/summary.html:91 |
|
4058 | #: rhodecode/templates/summary/summary.html:91 | |
4093 | #, python-format |
|
4059 | #, python-format | |
4094 | msgid "Non changable ID %s" |
|
4060 | msgid "Non changable ID %s" | |
4095 | msgstr "" |
|
4061 | msgstr "変更不能ID %s" | |
4096 |
|
4062 | |||
4097 | #: rhodecode/templates/summary/summary.html:96 |
|
4063 | #: rhodecode/templates/summary/summary.html:96 | |
4098 | msgid "public" |
|
4064 | msgid "public" | |
@@ -4100,7 +4066,7 b' msgstr "\xe5\x85\xac\xe9\x96\x8b"' | |||||
4100 |
|
4066 | |||
4101 | #: rhodecode/templates/summary/summary.html:104 |
|
4067 | #: rhodecode/templates/summary/summary.html:104 | |
4102 | msgid "remote clone" |
|
4068 | msgid "remote clone" | |
4103 | msgstr "" |
|
4069 | msgstr "リモートクローン" | |
4104 |
|
4070 | |||
4105 | #: rhodecode/templates/summary/summary.html:125 |
|
4071 | #: rhodecode/templates/summary/summary.html:125 | |
4106 | msgid "Contact" |
|
4072 | msgid "Contact" | |
@@ -4171,7 +4137,7 b' msgstr "\xe3\x82\xaf\xe3\x82\xa4\xe3\x83\x83\xe3\x82\xaf\xe3\x82\xb9\xe3\x82\xbf\xe3\x83\xbc\xe3\x83\x88"' | |||||
4171 | #: rhodecode/templates/summary/summary.html:243 |
|
4137 | #: rhodecode/templates/summary/summary.html:243 | |
4172 | #, python-format |
|
4138 | #, python-format | |
4173 | msgid "Readme file at revision '%s'" |
|
4139 | msgid "Readme file at revision '%s'" | |
4174 | msgstr "" |
|
4140 | msgstr "リビジョン '%s' のReadmeファイル" | |
4175 |
|
4141 | |||
4176 | #: rhodecode/templates/summary/summary.html:246 |
|
4142 | #: rhodecode/templates/summary/summary.html:246 | |
4177 | msgid "Permalink to this readme" |
|
4143 | msgid "Permalink to this readme" | |
@@ -4220,9 +4186,8 b' msgid "%s Tags"' | |||||
4220 | msgstr "%s タグ" |
|
4186 | msgstr "%s タグ" | |
4221 |
|
4187 | |||
4222 | #: rhodecode/templates/tags/tags.html:29 |
|
4188 | #: rhodecode/templates/tags/tags.html:29 | |
4223 | #, fuzzy |
|
|||
4224 | msgid "Compare tags" |
|
4189 | msgid "Compare tags" | |
4225 | msgstr "比較" |
|
4190 | msgstr "タグの比較" | |
4226 |
|
4191 | |||
4227 | #~ msgid "" |
|
4192 | #~ msgid "" | |
4228 | #~ "%s repository is not mapped to db" |
|
4193 | #~ "%s repository is not mapped to db" |
This diff has been collapsed as it changes many lines, (519 lines changed) Show them Hide them | |||||
@@ -3,20 +3,21 b'' | |||||
3 | # This file is distributed under the same license as the rhodecode project. |
|
3 | # This file is distributed under the same license as the rhodecode project. | |
4 | # FIRST AUTHOR <EMAIL@ADDRESS>, 2010. |
|
4 | # FIRST AUTHOR <EMAIL@ADDRESS>, 2010. | |
5 | # Nemcio <bogdan114@g.pl>, 2012. |
|
5 | # Nemcio <bogdan114@g.pl>, 2012. | |
6 | # Nemo <areczek01@gmail.com>, 2012. |
|
6 | # Nemo <areczek01@gmail.com>, 2012, 2013. | |
7 | msgid "" |
|
7 | msgid "" | |
8 | msgstr "" |
|
8 | msgstr "" | |
9 | "Project-Id-Version: rhodecode 0.1\n" |
|
9 | "Project-Id-Version: rhodecode 0.1\n" | |
10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" |
|
10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | |
11 | "POT-Creation-Date: 2012-12-14 04:19+0100\n" |
|
11 | "POT-Creation-Date: 2012-12-14 04:19+0100\n" | |
12 |
"PO-Revision-Date: 201 |
|
12 | "PO-Revision-Date: 2013-01-18 18:12+0100\n" | |
13 |
"Last-Translator: Nem |
|
13 | "Last-Translator: Nemcio <bogdan114@g.pl>\n" | |
14 | "Language-Team: Test\n" |
|
14 | "Language-Team: Test\n" | |
15 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " |
|
15 | "Language: pl\n" | |
16 | "(n%100<10 || n%100>=20) ? 1 : 2)\n" |
|
|||
17 | "MIME-Version: 1.0\n" |
|
16 | "MIME-Version: 1.0\n" | |
18 | "Content-Type: text/plain; charset=utf-8\n" |
|
17 | "Content-Type: text/plain; charset=utf-8\n" | |
19 | "Content-Transfer-Encoding: 8bit\n" |
|
18 | "Content-Transfer-Encoding: 8bit\n" | |
|
19 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | |||
|
20 | "X-Generator: Virtaal 0.7.1\n" | |||
20 | "Generated-By: Babel 0.9.6\n" |
|
21 | "Generated-By: Babel 0.9.6\n" | |
21 |
|
22 | |||
22 | #: rhodecode/controllers/changelog.py:95 |
|
23 | #: rhodecode/controllers/changelog.py:95 | |
@@ -27,7 +28,8 b' msgstr "Wszystkie ga\xc5\x82\xc4\x99zie"' | |||||
27 | msgid "show white space" |
|
28 | msgid "show white space" | |
28 | msgstr "pokazuj spacje" |
|
29 | msgstr "pokazuj spacje" | |
29 |
|
30 | |||
30 |
#: rhodecode/controllers/changeset.py:90 |
|
31 | #: rhodecode/controllers/changeset.py:90 | |
|
32 | #: rhodecode/controllers/changeset.py:97 | |||
31 | msgid "ignore white space" |
|
33 | msgid "ignore white space" | |
32 | msgstr "ignoruj pokazywanie spacji" |
|
34 | msgstr "ignoruj pokazywanie spacji" | |
33 |
|
35 | |||
@@ -43,12 +45,8 b' msgid "Status change -> %s"' | |||||
43 | msgstr "Zmiana statusu -> %s" |
|
45 | msgstr "Zmiana statusu -> %s" | |
44 |
|
46 | |||
45 | #: rhodecode/controllers/changeset.py:345 |
|
47 | #: rhodecode/controllers/changeset.py:345 | |
46 | msgid "" |
|
48 | msgid "Changing status on a changeset associated witha closed pull request is not allowed" | |
47 | "Changing status on a changeset associated witha closed pull request is " |
|
49 | msgstr "Zmiana statusu na grupy zmian powiązania łączy zamkniętego wniosku jest niedozwolona" | |
48 | "not allowed" |
|
|||
49 | msgstr "" |
|
|||
50 | "Zmiana statusu na grupy zmian powiązania łączy zamkniętego wniosku jest " |
|
|||
51 | "niedozwolona" |
|
|||
52 |
|
50 | |||
53 | #: rhodecode/controllers/compare.py:75 |
|
51 | #: rhodecode/controllers/compare.py:75 | |
54 | #: rhodecode/controllers/pullrequests.py:121 |
|
52 | #: rhodecode/controllers/pullrequests.py:121 | |
@@ -62,9 +60,7 b' msgstr "Strona g\xc5\x82\xc3\xb3wna"' | |||||
62 |
|
60 | |||
63 | #: rhodecode/controllers/error.py:98 |
|
61 | #: rhodecode/controllers/error.py:98 | |
64 | msgid "The request could not be understood by the server due to malformed syntax." |
|
62 | msgid "The request could not be understood by the server due to malformed syntax." | |
65 | msgstr "" |
|
63 | msgstr "Wniosek nie może być rozumiany przez serwer z powodu zniekształconej składni." | |
66 | "Wniosek nie może być rozumiany przez serwer z powodu zniekształconej " |
|
|||
67 | "składni." |
|
|||
68 |
|
64 | |||
69 | #: rhodecode/controllers/error.py:101 |
|
65 | #: rhodecode/controllers/error.py:101 | |
70 | msgid "Unauthorized access to resource" |
|
66 | msgid "Unauthorized access to resource" | |
@@ -79,12 +75,8 b' msgid "The resource could not be found"' | |||||
79 | msgstr "Zasób nie został znaleziony" |
|
75 | msgstr "Zasób nie został znaleziony" | |
80 |
|
76 | |||
81 | #: rhodecode/controllers/error.py:107 |
|
77 | #: rhodecode/controllers/error.py:107 | |
82 | msgid "" |
|
78 | msgid "The server encountered an unexpected condition which prevented it from fulfilling the request." | |
83 | "The server encountered an unexpected condition which prevented it from " |
|
79 | msgstr "Serwer napotkał niespodziewany warunek, który uniemożliwia jej spełnienie żądania." | |
84 | "fulfilling the request." |
|
|||
85 | msgstr "" |
|
|||
86 | "Serwer napotkał niespodziewany warunek, który uniemożliwia jej spełnienie" |
|
|||
87 | " żądania." |
|
|||
88 |
|
80 | |||
89 | #: rhodecode/controllers/feed.py:52 |
|
81 | #: rhodecode/controllers/feed.py:52 | |
90 | #, python-format |
|
82 | #, python-format | |
@@ -119,7 +111,8 b' msgstr "Kliknij tutaj, by doda\xc4\x87 nowy plik"' | |||||
119 | msgid "There are no files yet %s" |
|
111 | msgid "There are no files yet %s" | |
120 | msgstr "Brak plików %s" |
|
112 | msgstr "Brak plików %s" | |
121 |
|
113 | |||
122 |
#: rhodecode/controllers/files.py:265 |
|
114 | #: rhodecode/controllers/files.py:265 | |
|
115 | #: rhodecode/controllers/files.py:325 | |||
123 | #, python-format |
|
116 | #, python-format | |
124 | msgid "This repository is has been locked by %s on %s" |
|
117 | msgid "This repository is has been locked by %s on %s" | |
125 | msgstr "Repozytorium zostało zablokowane przez %s na %s" |
|
118 | msgstr "Repozytorium zostało zablokowane przez %s na %s" | |
@@ -133,12 +126,14 b' msgstr "Edytowanie %s w RhodeCode"' | |||||
133 | msgid "No changes" |
|
126 | msgid "No changes" | |
134 | msgstr "Bez zmian" |
|
127 | msgstr "Bez zmian" | |
135 |
|
128 | |||
136 |
#: rhodecode/controllers/files.py:308 |
|
129 | #: rhodecode/controllers/files.py:308 | |
|
130 | #: rhodecode/controllers/files.py:372 | |||
137 | #, python-format |
|
131 | #, python-format | |
138 | msgid "Successfully committed to %s" |
|
132 | msgid "Successfully committed to %s" | |
139 | msgstr "Committ wykonany do %s" |
|
133 | msgstr "Committ wykonany do %s" | |
140 |
|
134 | |||
141 |
#: rhodecode/controllers/files.py:313 |
|
135 | #: rhodecode/controllers/files.py:313 | |
|
136 | #: rhodecode/controllers/files.py:378 | |||
142 | msgid "Error occurred during commit" |
|
137 | msgid "Error occurred during commit" | |
143 | msgstr "Wystąpił błąd w trakcie zatwierdzania" |
|
138 | msgstr "Wystąpił błąd w trakcie zatwierdzania" | |
144 |
|
139 | |||
@@ -178,13 +173,17 b' msgstr "Nieznany typ archiwum"' | |||||
178 | msgid "Changesets" |
|
173 | msgid "Changesets" | |
179 | msgstr "Różnice" |
|
174 | msgstr "Różnice" | |
180 |
|
175 | |||
181 |
#: rhodecode/controllers/files.py:565 |
|
176 | #: rhodecode/controllers/files.py:565 | |
182 | #: rhodecode/controllers/summary.py:236 rhodecode/model/scm.py:550 |
|
177 | #: rhodecode/controllers/pullrequests.py:74 | |
|
178 | #: rhodecode/controllers/summary.py:236 | |||
|
179 | #: rhodecode/model/scm.py:550 | |||
183 | msgid "Branches" |
|
180 | msgid "Branches" | |
184 | msgstr "Gałęzie" |
|
181 | msgstr "Gałęzie" | |
185 |
|
182 | |||
186 |
#: rhodecode/controllers/files.py:566 |
|
183 | #: rhodecode/controllers/files.py:566 | |
187 | #: rhodecode/controllers/summary.py:237 rhodecode/model/scm.py:561 |
|
184 | #: rhodecode/controllers/pullrequests.py:78 | |
|
185 | #: rhodecode/controllers/summary.py:237 | |||
|
186 | #: rhodecode/model/scm.py:561 | |||
188 | msgid "Tags" |
|
187 | msgid "Tags" | |
189 | msgstr "Etykiety" |
|
188 | msgstr "Etykiety" | |
190 |
|
189 | |||
@@ -198,11 +197,13 b' msgstr "ga\xc5\x82\xc4\x99zi %s w repozytorium %s"' | |||||
198 | msgid "An error occurred during repository forking %s" |
|
197 | msgid "An error occurred during repository forking %s" | |
199 | msgstr "Wystąpił błąd podczas rozgałęzienia %s repozytorium" |
|
198 | msgstr "Wystąpił błąd podczas rozgałęzienia %s repozytorium" | |
200 |
|
199 | |||
201 |
#: rhodecode/controllers/journal.py:218 |
|
200 | #: rhodecode/controllers/journal.py:218 | |
|
201 | #: rhodecode/controllers/journal.py:261 | |||
202 | msgid "public journal" |
|
202 | msgid "public journal" | |
203 | msgstr "Dziennik publiczny" |
|
203 | msgstr "Dziennik publiczny" | |
204 |
|
204 | |||
205 |
#: rhodecode/controllers/journal.py:222 |
|
205 | #: rhodecode/controllers/journal.py:222 | |
|
206 | #: rhodecode/controllers/journal.py:265 | |||
206 | #: rhodecode/templates/base/base.html:232 |
|
207 | #: rhodecode/templates/base/base.html:232 | |
207 | #: rhodecode/templates/journal/journal.html:12 |
|
208 | #: rhodecode/templates/journal/journal.html:12 | |
208 | msgid "journal" |
|
209 | msgid "journal" | |
@@ -217,12 +218,11 b' msgid "Your password reset link was sent' | |||||
217 | msgstr "Twój link zresetowania hasła został wysłany" |
|
218 | msgstr "Twój link zresetowania hasła został wysłany" | |
218 |
|
219 | |||
219 | #: rhodecode/controllers/login.py:184 |
|
220 | #: rhodecode/controllers/login.py:184 | |
220 | msgid "" |
|
221 | msgid "Your password reset was successful, new password has been sent to your email" | |
221 | "Your password reset was successful, new password has been sent to your " |
|
|||
222 | "email" |
|
|||
223 | msgstr "Twoje hasło zostało zresetowane, nowe hasło zostanie wysłane na e-mail" |
|
222 | msgstr "Twoje hasło zostało zresetowane, nowe hasło zostanie wysłane na e-mail" | |
224 |
|
223 | |||
225 |
#: rhodecode/controllers/pullrequests.py:76 |
|
224 | #: rhodecode/controllers/pullrequests.py:76 | |
|
225 | #: rhodecode/model/scm.py:556 | |||
226 | msgid "Bookmarks" |
|
226 | msgid "Bookmarks" | |
227 | msgstr "Zakładki" |
|
227 | msgstr "Zakładki" | |
228 |
|
228 | |||
@@ -247,8 +247,9 b' msgid "Successfully deleted pull request' | |||||
247 | msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo" |
|
247 | msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo" | |
248 |
|
248 | |||
249 | #: rhodecode/controllers/pullrequests.py:452 |
|
249 | #: rhodecode/controllers/pullrequests.py:452 | |
|
250 | #, fuzzy | |||
250 | msgid "Closing pull request on other statuses than rejected or approved forbidden" |
|
251 | msgid "Closing pull request on other statuses than rejected or approved forbidden" | |
251 | msgstr "" |
|
252 | msgstr "Zamknij wszystkie wnioski połączenia gałęzi innych stanów niż odrzucony, zatwierdzony lub zabroniony" | |
252 |
|
253 | |||
253 | #: rhodecode/controllers/search.py:134 |
|
254 | #: rhodecode/controllers/search.py:134 | |
254 | msgid "Invalid search query. Try quoting it." |
|
255 | msgid "Invalid search query. Try quoting it." | |
@@ -315,14 +316,12 b' msgid "Statistics are disabled for this ' | |||||
315 | msgstr "Statystyki są wyłączone dla tego repozytorium" |
|
316 | msgstr "Statystyki są wyłączone dla tego repozytorium" | |
316 |
|
317 | |||
317 | #: rhodecode/controllers/admin/defaults.py:96 |
|
318 | #: rhodecode/controllers/admin/defaults.py:96 | |
318 | #, fuzzy |
|
|||
319 | msgid "Default settings updated successfully" |
|
319 | msgid "Default settings updated successfully" | |
320 |
msgstr " |
|
320 | msgstr "Domyślne ustawienia zostały pomyślnie zaktualizowane" | |
321 |
|
321 | |||
322 | #: rhodecode/controllers/admin/defaults.py:110 |
|
322 | #: rhodecode/controllers/admin/defaults.py:110 | |
323 | #, fuzzy |
|
|||
324 | msgid "error occurred during update of defaults" |
|
323 | msgid "error occurred during update of defaults" | |
325 |
msgstr "wystąpił błąd podczas aktualizacji |
|
324 | msgstr "wystąpił błąd podczas aktualizacji wartości domyślnych" | |
326 |
|
325 | |||
327 | #: rhodecode/controllers/admin/ldap_settings.py:50 |
|
326 | #: rhodecode/controllers/admin/ldap_settings.py:50 | |
328 | msgid "BASE" |
|
327 | msgid "BASE" | |
@@ -500,7 +499,8 b' msgstr "Zaktualizowano widoczno\xc5\x9b\xc4\x87 stron w publicznym dzienniku"' | |||||
500 | msgid "An error occurred during setting this repository in public journal" |
|
499 | msgid "An error occurred during setting this repository in public journal" | |
501 | msgstr "Wystąpił błąd podczas ustawiania tego repozytorium w dzienniku publicznym" |
|
500 | msgstr "Wystąpił błąd podczas ustawiania tego repozytorium w dzienniku publicznym" | |
502 |
|
501 | |||
503 |
#: rhodecode/controllers/admin/repos.py:452 |
|
502 | #: rhodecode/controllers/admin/repos.py:452 | |
|
503 | #: rhodecode/model/validators.py:300 | |||
504 | msgid "Token mismatch" |
|
504 | msgid "Token mismatch" | |
505 | msgstr "Niezgodność tokenu" |
|
505 | msgstr "Niezgodność tokenu" | |
506 |
|
506 | |||
@@ -576,9 +576,7 b' msgstr "Wyst\xc4\x85pi\xc5\x82 b\xc5\x82\xc4\x85d podczas usuwania grup i grup u\xc5\xbcytkownik\xc3\xb3w"' | |||||
576 | #: rhodecode/controllers/admin/settings.py:123 |
|
576 | #: rhodecode/controllers/admin/settings.py:123 | |
577 | #, python-format |
|
577 | #, python-format | |
578 | msgid "Repositories successfully rescanned added: %s,removed: %s" |
|
578 | msgid "Repositories successfully rescanned added: %s,removed: %s" | |
579 | msgstr "" |
|
579 | msgstr "Repozytoria z powodzeniem zostały ponownie zeskanowane dodano: %s, usunięto: %s" | |
580 | "Repozytoria z powodzeniem zostały ponownie zeskanowane dodano: %s, " |
|
|||
581 | "usunięto: %s" |
|
|||
582 |
|
580 | |||
583 | #: rhodecode/controllers/admin/settings.py:131 |
|
581 | #: rhodecode/controllers/admin/settings.py:131 | |
584 | msgid "Whoosh reindex task scheduled" |
|
582 | msgid "Whoosh reindex task scheduled" | |
@@ -623,9 +621,7 b' msgstr "E-mail zosta\xc5\x82 wys\xc5\x82any"' | |||||
623 |
|
621 | |||
624 | #: rhodecode/controllers/admin/settings.py:399 |
|
622 | #: rhodecode/controllers/admin/settings.py:399 | |
625 | msgid "You can't edit this user since it's crucial for entire application" |
|
623 | msgid "You can't edit this user since it's crucial for entire application" | |
626 | msgstr "" |
|
624 | msgstr "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej aplikacji" | |
627 | "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej " |
|
|||
628 | "aplikacji" |
|
|||
629 |
|
625 | |||
630 | #: rhodecode/controllers/admin/settings.py:430 |
|
626 | #: rhodecode/controllers/admin/settings.py:430 | |
631 | msgid "Your account was updated successfully" |
|
627 | msgid "Your account was updated successfully" | |
@@ -755,9 +751,7 b' msgstr "plik binarny"' | |||||
755 |
|
751 | |||
756 | #: rhodecode/lib/diffs.py:90 |
|
752 | #: rhodecode/lib/diffs.py:90 | |
757 | msgid "Changeset was too big and was cut off, use diff menu to display this diff" |
|
753 | msgid "Changeset was too big and was cut off, use diff menu to display this diff" | |
758 | msgstr "" |
|
754 | msgstr "Lista zmian była zbyt duża i została obcięta, użyj menu porównań żeby wyświetlić różnice" | |
759 | "Lista zmian była zbyt duża i została obcięta, użyj menu porównań żeby " |
|
|||
760 | "wyświetlić różnice" |
|
|||
761 |
|
755 | |||
762 | #: rhodecode/lib/diffs.py:100 |
|
756 | #: rhodecode/lib/diffs.py:100 | |
763 | msgid "No changes detected" |
|
757 | msgid "No changes detected" | |
@@ -808,7 +802,8 b' msgstr "i"' | |||||
808 | msgid "%s more" |
|
802 | msgid "%s more" | |
809 | msgstr "%s więcej" |
|
803 | msgstr "%s więcej" | |
810 |
|
804 | |||
811 | #: rhodecode/lib/helpers.py:617 rhodecode/templates/changelog/changelog.html:51 |
|
805 | #: rhodecode/lib/helpers.py:617 | |
|
806 | #: rhodecode/templates/changelog/changelog.html:51 | |||
812 | msgid "revisions" |
|
807 | msgid "revisions" | |
813 | msgstr "rewizja" |
|
808 | msgstr "rewizja" | |
814 |
|
809 | |||
@@ -828,7 +823,8 b' msgstr "Po\xc5\x82\xc4\x85czonych ga\xc5\x82\xc4\x99zi #%s"' | |||||
828 | msgid "[deleted] repository" |
|
823 | msgid "[deleted] repository" | |
829 | msgstr "[usunięte] repozytorium" |
|
824 | msgstr "[usunięte] repozytorium" | |
830 |
|
825 | |||
831 |
#: rhodecode/lib/helpers.py:666 |
|
826 | #: rhodecode/lib/helpers.py:666 | |
|
827 | #: rhodecode/lib/helpers.py:676 | |||
832 | msgid "[created] repository" |
|
828 | msgid "[created] repository" | |
833 | msgstr "[utworzone] repozytorium" |
|
829 | msgstr "[utworzone] repozytorium" | |
834 |
|
830 | |||
@@ -836,11 +832,13 b' msgstr "[utworzone] repozytorium"' | |||||
836 | msgid "[created] repository as fork" |
|
832 | msgid "[created] repository as fork" | |
837 | msgstr "[utworzone] repozytorium jako rozgałęzienie" |
|
833 | msgstr "[utworzone] repozytorium jako rozgałęzienie" | |
838 |
|
834 | |||
839 |
#: rhodecode/lib/helpers.py:670 |
|
835 | #: rhodecode/lib/helpers.py:670 | |
|
836 | #: rhodecode/lib/helpers.py:678 | |||
840 | msgid "[forked] repository" |
|
837 | msgid "[forked] repository" | |
841 | msgstr "[rozgałęzione] repozytorium" |
|
838 | msgstr "[rozgałęzione] repozytorium" | |
842 |
|
839 | |||
843 |
#: rhodecode/lib/helpers.py:672 |
|
840 | #: rhodecode/lib/helpers.py:672 | |
|
841 | #: rhodecode/lib/helpers.py:680 | |||
844 | msgid "[updated] repository" |
|
842 | msgid "[updated] repository" | |
845 | msgstr "[zaktualizowane] repozytorium" |
|
843 | msgstr "[zaktualizowane] repozytorium" | |
846 |
|
844 | |||
@@ -911,14 +909,8 b' msgstr "Brak Plik\xc3\xb3w"' | |||||
911 |
|
909 | |||
912 | #: rhodecode/lib/helpers.py:1163 |
|
910 | #: rhodecode/lib/helpers.py:1163 | |
913 | #, python-format |
|
911 | #, python-format | |
914 | msgid "" |
|
912 | 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" | |
915 | "%s repository is not mapped to db perhaps it was created or renamed from " |
|
913 | msgstr "%s repozytorium nie jest mapowane do db może zostało utworzone lub zmienione z systemie plików proszę uruchomić aplikację ponownie, aby ponownie przeskanować repozytoria" | |
916 | "the filesystem please run the application again in order to rescan " |
|
|||
917 | "repositories" |
|
|||
918 | msgstr "" |
|
|||
919 | "%s repozytorium nie jest mapowane do db może zostało utworzone lub " |
|
|||
920 | "zmienione z systemie plików proszę uruchomić aplikację ponownie, aby " |
|
|||
921 | "ponownie przeskanować repozytoria" |
|
|||
922 |
|
914 | |||
923 | #: rhodecode/lib/utils2.py:403 |
|
915 | #: rhodecode/lib/utils2.py:403 | |
924 | #, python-format |
|
916 | #, python-format | |
@@ -996,83 +988,103 b' msgstr "przed chwil\xc4\x85"' | |||||
996 | msgid "password reset link" |
|
988 | msgid "password reset link" | |
997 | msgstr "łącze resetowania hasła" |
|
989 | msgstr "łącze resetowania hasła" | |
998 |
|
990 | |||
999 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1163 |
|
991 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1163 | |
|
992 | #: rhodecode/model/db.py:1183 | |||
1000 | msgid "Repository no access" |
|
993 | msgid "Repository no access" | |
1001 | msgstr "Brak dostępu do repozytorium" |
|
994 | msgstr "Brak dostępu do repozytorium" | |
1002 |
|
995 | |||
1003 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1164 |
|
996 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1164 | |
|
997 | #: rhodecode/model/db.py:1184 | |||
1004 | msgid "Repository read access" |
|
998 | msgid "Repository read access" | |
1005 | msgstr "Repozytorium do odczytu" |
|
999 | msgstr "Repozytorium do odczytu" | |
1006 |
|
1000 | |||
1007 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1165 |
|
1001 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1165 | |
|
1002 | #: rhodecode/model/db.py:1185 | |||
1008 | msgid "Repository write access" |
|
1003 | msgid "Repository write access" | |
1009 | msgstr "Repozytorium do zapisu" |
|
1004 | msgstr "Repozytorium do zapisu" | |
1010 |
|
1005 | |||
1011 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1166 |
|
1006 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1166 | |
|
1007 | #: rhodecode/model/db.py:1186 | |||
1012 | msgid "Repository admin access" |
|
1008 | msgid "Repository admin access" | |
1013 | msgstr "Administracja dostępu do repozytorium" |
|
1009 | msgstr "Administracja dostępu do repozytorium" | |
1014 |
|
1010 | |||
1015 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1168 |
|
1011 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1168 | |
|
1012 | #: rhodecode/model/db.py:1188 | |||
1016 | msgid "Repositories Group no access" |
|
1013 | msgid "Repositories Group no access" | |
1017 | msgstr "Grupy repozytoriów brak dostępu" |
|
1014 | msgstr "Grupy repozytoriów brak dostępu" | |
1018 |
|
1015 | |||
1019 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1169 |
|
1016 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1169 | |
|
1017 | #: rhodecode/model/db.py:1189 | |||
1020 | msgid "Repositories Group read access" |
|
1018 | msgid "Repositories Group read access" | |
1021 | msgstr "Grupy repozytoriów dostęp do odczytu" |
|
1019 | msgstr "Grupy repozytoriów dostęp do odczytu" | |
1022 |
|
1020 | |||
1023 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1170 |
|
1021 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1170 | |
|
1022 | #: rhodecode/model/db.py:1190 | |||
1024 | msgid "Repositories Group write access" |
|
1023 | msgid "Repositories Group write access" | |
1025 | msgstr "Grupy repozytoriów dostęp do zapisu" |
|
1024 | msgstr "Grupy repozytoriów dostęp do zapisu" | |
1026 |
|
1025 | |||
1027 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1171 |
|
1026 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1171 | |
|
1027 | #: rhodecode/model/db.py:1191 | |||
1028 | msgid "Repositories Group admin access" |
|
1028 | msgid "Repositories Group admin access" | |
1029 | msgstr "Repozytoria Grupy dostęp administratora" |
|
1029 | msgstr "Repozytoria Grupy dostęp administratora" | |
1030 |
|
1030 | |||
1031 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1173 |
|
1031 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1173 | |
|
1032 | #: rhodecode/model/db.py:1193 | |||
1032 | msgid "RhodeCode Administrator" |
|
1033 | msgid "RhodeCode Administrator" | |
1033 | msgstr "Administrator Repo" |
|
1034 | msgstr "Administrator Repo" | |
1034 |
|
1035 | |||
1035 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1174 |
|
1036 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1174 | |
|
1037 | #: rhodecode/model/db.py:1194 | |||
1036 | msgid "Repository creation disabled" |
|
1038 | msgid "Repository creation disabled" | |
1037 | msgstr "Repozytorium wyłączone" |
|
1039 | msgstr "Repozytorium wyłączone" | |
1038 |
|
1040 | |||
1039 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1175 |
|
1041 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1175 | |
|
1042 | #: rhodecode/model/db.py:1195 | |||
1040 | msgid "Repository creation enabled" |
|
1043 | msgid "Repository creation enabled" | |
1041 | msgstr "Repozytorium włączone" |
|
1044 | msgstr "Repozytorium włączone" | |
1042 |
|
1045 | |||
1043 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1176 |
|
1046 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1176 | |
|
1047 | #: rhodecode/model/db.py:1196 | |||
1044 | msgid "Repository forking disabled" |
|
1048 | msgid "Repository forking disabled" | |
1045 | msgstr "Rozwidlenie repozytorium wyłączone" |
|
1049 | msgstr "Rozwidlenie repozytorium wyłączone" | |
1046 |
|
1050 | |||
1047 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1177 |
|
1051 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1177 | |
|
1052 | #: rhodecode/model/db.py:1197 | |||
1048 | msgid "Repository forking enabled" |
|
1053 | msgid "Repository forking enabled" | |
1049 | msgstr "Rozwidlenie repozytorium włączone" |
|
1054 | msgstr "Rozwidlenie repozytorium włączone" | |
1050 |
|
1055 | |||
1051 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1178 |
|
1056 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1178 | |
|
1057 | #: rhodecode/model/db.py:1198 | |||
1052 | msgid "Register disabled" |
|
1058 | msgid "Register disabled" | |
1053 | msgstr "Rejestracja wyłączona" |
|
1059 | msgstr "Rejestracja wyłączona" | |
1054 |
|
1060 | |||
1055 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1179 |
|
1061 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1179 | |
|
1062 | #: rhodecode/model/db.py:1199 | |||
1056 | msgid "Register new user with RhodeCode with manual activation" |
|
1063 | msgid "Register new user with RhodeCode with manual activation" | |
1057 | msgstr "Rejestracja nowego użytkownika na stronie z ręczną aktywacją" |
|
1064 | msgstr "Rejestracja nowego użytkownika na stronie z ręczną aktywacją" | |
1058 |
|
1065 | |||
1059 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1182 |
|
1066 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1182 | |
|
1067 | #: rhodecode/model/db.py:1202 | |||
1060 | msgid "Register new user with RhodeCode with auto activation" |
|
1068 | msgid "Register new user with RhodeCode with auto activation" | |
1061 | msgstr "Rejestracja nowego użytkownika na stronie z automatyczną aktywacją" |
|
1069 | msgstr "Rejestracja nowego użytkownika na stronie z automatyczną aktywacją" | |
1062 |
|
1070 | |||
1063 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1623 |
|
1071 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1623 | |
|
1072 | #: rhodecode/model/db.py:1643 | |||
1064 | msgid "Not Reviewed" |
|
1073 | msgid "Not Reviewed" | |
1065 | msgstr "Brak Korekty" |
|
1074 | msgstr "Brak Korekty" | |
1066 |
|
1075 | |||
1067 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1624 |
|
1076 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1624 | |
|
1077 | #: rhodecode/model/db.py:1644 | |||
1068 | msgid "Approved" |
|
1078 | msgid "Approved" | |
1069 | msgstr "Zaakceptowano" |
|
1079 | msgstr "Zaakceptowano" | |
1070 |
|
1080 | |||
1071 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1625 |
|
1081 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1625 | |
|
1082 | #: rhodecode/model/db.py:1645 | |||
1072 | msgid "Rejected" |
|
1083 | msgid "Rejected" | |
1073 | msgstr "Odrzucono" |
|
1084 | msgstr "Odrzucono" | |
1074 |
|
1085 | |||
1075 |
#: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1626 |
|
1086 | #: rhodecode/lib/dbmigrate/schema/db_1_4_0.py:1626 | |
|
1087 | #: rhodecode/model/db.py:1646 | |||
1076 | msgid "Under Review" |
|
1088 | msgid "Under Review" | |
1077 | msgstr "Objęty Przeglądem" |
|
1089 | msgstr "Objęty Przeglądem" | |
1078 |
|
1090 | |||
@@ -1146,29 +1158,23 b' msgstr "ostatni tip"' | |||||
1146 | msgid "new user registration" |
|
1158 | msgid "new user registration" | |
1147 | msgstr "nowy użytkownik się zarejestrował" |
|
1159 | msgstr "nowy użytkownik się zarejestrował" | |
1148 |
|
1160 | |||
1149 |
#: rhodecode/model/user.py:257 |
|
1161 | #: rhodecode/model/user.py:257 | |
|
1162 | #: rhodecode/model/user.py:281 | |||
1150 | #: rhodecode/model/user.py:303 |
|
1163 | #: rhodecode/model/user.py:303 | |
1151 | msgid "You can't Edit this user since it's crucial for entire application" |
|
1164 | msgid "You can't Edit this user since it's crucial for entire application" | |
1152 | msgstr "" |
|
1165 | msgstr "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej aplikacji" | |
1153 | "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej " |
|
|||
1154 | "aplikacji" |
|
|||
1155 |
|
1166 | |||
1156 | #: rhodecode/model/user.py:327 |
|
1167 | #: rhodecode/model/user.py:327 | |
1157 | msgid "You can't remove this user since it's crucial for entire application" |
|
1168 | msgid "You can't remove this user since it's crucial for entire application" | |
1158 | msgstr "" |
|
1169 | msgstr "Nie możesz usunąć tego użytkownika ponieważ jest kluczowy dla całej aplikacji" | |
1159 | "Nie możesz usunąć tego użytkownika ponieważ jest kluczowy dla całej " |
|
|||
1160 | "aplikacji" |
|
|||
1161 |
|
1170 | |||
1162 | #: rhodecode/model/user.py:333 |
|
1171 | #: rhodecode/model/user.py:333 | |
1163 | #, python-format |
|
1172 | #, python-format | |
1164 | msgid "" |
|
1173 | msgid "user \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories. %s" | |
1165 | "user \"%s\" still owns %s repositories and cannot be removed. Switch " |
|
1174 | msgstr "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s" | |
1166 | "owners or remove those repositories. %s" |
|
1175 | ||
1167 | msgstr "" |
|
1176 | #: rhodecode/model/validators.py:36 | |
1168 | "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może " |
|
1177 | #: rhodecode/model/validators.py:37 | |
1169 | "zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s" |
|
|||
1170 |
|
||||
1171 | #: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37 |
|
|||
1172 | msgid "Value cannot be an empty list" |
|
1178 | msgid "Value cannot be an empty list" | |
1173 | msgstr "Wartość listy nie może być pusta" |
|
1179 | msgstr "Wartość listy nie może być pusta" | |
1174 |
|
1180 | |||
@@ -1183,12 +1189,8 b' msgid "Username \\"%(username)s\\" is forb' | |||||
1183 | msgstr "Nazwa użytkownika \"%(username)s\" jest zabroniona" |
|
1189 | msgstr "Nazwa użytkownika \"%(username)s\" jest zabroniona" | |
1184 |
|
1190 | |||
1185 | #: rhodecode/model/validators.py:87 |
|
1191 | #: rhodecode/model/validators.py:87 | |
1186 | msgid "" |
|
1192 | msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character" | |
1187 | "Username may only contain alphanumeric characters underscores, periods or" |
|
1193 | msgstr "Nazwa użytkownika może zawierać tylko znaki alfanumeryczne, podkreślenia, kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym" | |
1188 | " dashes and must begin with alphanumeric character" |
|
|||
1189 | msgstr "" |
|
|||
1190 | "Nazwa użytkownika może zawierać tylko znaki alfanumeryczne, podkreślenia," |
|
|||
1191 | " kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym" |
|
|||
1192 |
|
1194 | |||
1193 | #: rhodecode/model/validators.py:115 |
|
1195 | #: rhodecode/model/validators.py:115 | |
1194 | #, python-format |
|
1196 | #, python-format | |
@@ -1205,12 +1207,8 b' msgid "Users group \\"%(usersgroup)s\\" al' | |||||
1205 | msgstr "Nazwa grupy \"%(usersgroup)s\" już istnieje" |
|
1207 | msgstr "Nazwa grupy \"%(usersgroup)s\" już istnieje" | |
1206 |
|
1208 | |||
1207 | #: rhodecode/model/validators.py:137 |
|
1209 | #: rhodecode/model/validators.py:137 | |
1208 | msgid "" |
|
1210 | msgid "users group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character" | |
1209 | "users group name may only contain alphanumeric characters underscores, " |
|
1211 | msgstr "Nazwa grupy może zawierać tylko znaki alfanumeryczne, podkreślenia, kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym" | |
1210 | "periods or dashes and must begin with alphanumeric character" |
|
|||
1211 | msgstr "" |
|
|||
1212 | "Nazwa grupy może zawierać tylko znaki alfanumeryczne, podkreślenia, " |
|
|||
1213 | "kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym" |
|
|||
1214 |
|
1212 | |||
1215 | #: rhodecode/model/validators.py:175 |
|
1213 | #: rhodecode/model/validators.py:175 | |
1216 | msgid "Cannot assign this group as parent" |
|
1214 | msgid "Cannot assign this group as parent" | |
@@ -1300,12 +1298,8 b' msgid "e-mail \\"%(email)s\\" does not exi' | |||||
1300 | msgstr "e-mail \"%(email)s\" nie istnieje." |
|
1298 | msgstr "e-mail \"%(email)s\" nie istnieje." | |
1301 |
|
1299 | |||
1302 | #: rhodecode/model/validators.py:663 |
|
1300 | #: rhodecode/model/validators.py:663 | |
1303 | msgid "" |
|
1301 | msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\"" | |
1304 | "The LDAP Login attribute of the CN must be specified - this is the name " |
|
1302 | msgstr "Atrybut logowania CN do LDAP należy określić, jest to nazwa atrybutu, który jest odpowiednikiem \"username\"" | |
1305 | "of the attribute that is equivalent to \"username\"" |
|
|||
1306 | msgstr "" |
|
|||
1307 | "Atrybut logowania CN do LDAP należy określić, jest to nazwa atrybutu, " |
|
|||
1308 | "który jest odpowiednikiem \"username\"" |
|
|||
1309 |
|
1303 | |||
1310 | #: rhodecode/model/validators.py:682 |
|
1304 | #: rhodecode/model/validators.py:682 | |
1311 | #, python-format |
|
1305 | #, python-format | |
@@ -1498,7 +1492,8 b' msgstr "B\xc5\x82\xc4\x85d danych."' | |||||
1498 | msgid "Loading..." |
|
1492 | msgid "Loading..." | |
1499 | msgstr "Wczytywanie..." |
|
1493 | msgstr "Wczytywanie..." | |
1500 |
|
1494 | |||
1501 |
#: rhodecode/templates/login.html:5 |
|
1495 | #: rhodecode/templates/login.html:5 | |
|
1496 | #: rhodecode/templates/login.html:54 | |||
1502 | msgid "Sign In" |
|
1497 | msgid "Sign In" | |
1503 | msgstr "Zaloguj się" |
|
1498 | msgstr "Zaloguj się" | |
1504 |
|
1499 | |||
@@ -1506,7 +1501,8 b' msgstr "Zaloguj si\xc4\x99"' | |||||
1506 | msgid "Sign In to" |
|
1501 | msgid "Sign In to" | |
1507 | msgstr "Zarejestruj się" |
|
1502 | msgstr "Zarejestruj się" | |
1508 |
|
1503 | |||
1509 |
#: rhodecode/templates/login.html:31 |
|
1504 | #: rhodecode/templates/login.html:31 | |
|
1505 | #: rhodecode/templates/register.html:20 | |||
1510 | #: rhodecode/templates/admin/admin_log.html:5 |
|
1506 | #: rhodecode/templates/admin/admin_log.html:5 | |
1511 | #: rhodecode/templates/admin/users/user_add.html:32 |
|
1507 | #: rhodecode/templates/admin/users/user_add.html:32 | |
1512 | #: rhodecode/templates/admin/users/user_edit.html:50 |
|
1508 | #: rhodecode/templates/admin/users/user_edit.html:50 | |
@@ -1516,7 +1512,8 b' msgstr "Zarejestruj si\xc4\x99"' | |||||
1516 | msgid "Username" |
|
1512 | msgid "Username" | |
1517 | msgstr "Nazwa użytkownika" |
|
1513 | msgstr "Nazwa użytkownika" | |
1518 |
|
1514 | |||
1519 |
#: rhodecode/templates/login.html:40 |
|
1515 | #: rhodecode/templates/login.html:40 | |
|
1516 | #: rhodecode/templates/register.html:29 | |||
1520 | #: rhodecode/templates/admin/ldap/ldap.html:46 |
|
1517 | #: rhodecode/templates/admin/ldap/ldap.html:46 | |
1521 | #: rhodecode/templates/admin/users/user_add.html:41 |
|
1518 | #: rhodecode/templates/admin/users/user_add.html:41 | |
1522 | #: rhodecode/templates/base/base.html:92 |
|
1519 | #: rhodecode/templates/base/base.html:92 | |
@@ -1531,7 +1528,8 b' msgstr "Zapami\xc4\x99taj mnie"' | |||||
1531 | msgid "Forgot your password ?" |
|
1528 | msgid "Forgot your password ?" | |
1532 | msgstr "Zapomniałeś hasła?" |
|
1529 | msgstr "Zapomniałeś hasła?" | |
1533 |
|
1530 | |||
1534 |
#: rhodecode/templates/login.html:63 |
|
1531 | #: rhodecode/templates/login.html:63 | |
|
1532 | #: rhodecode/templates/base/base.html:103 | |||
1535 | msgid "Don't have an account ?" |
|
1533 | msgid "Don't have an account ?" | |
1536 | msgstr "Nie masz konta?" |
|
1534 | msgstr "Nie masz konta?" | |
1537 |
|
1535 | |||
@@ -1555,7 +1553,8 b' msgstr "Zresetuj swoje has\xc5\x82o"' | |||||
1555 | msgid "Password reset link will be send to matching email address" |
|
1553 | msgid "Password reset link will be send to matching email address" | |
1556 | msgstr "Link do zresetowania hasła zostanie wysłany na adres e-mail" |
|
1554 | msgstr "Link do zresetowania hasła zostanie wysłany na adres e-mail" | |
1557 |
|
1555 | |||
1558 |
#: rhodecode/templates/register.html:5 |
|
1556 | #: rhodecode/templates/register.html:5 | |
|
1557 | #: rhodecode/templates/register.html:74 | |||
1559 | msgid "Sign Up" |
|
1558 | msgid "Sign Up" | |
1560 | msgstr "Zarejestruj się" |
|
1559 | msgstr "Zarejestruj się" | |
1561 |
|
1560 | |||
@@ -1646,24 +1645,22 b' msgid "Admin journal"' | |||||
1646 | msgstr "Dziennik administratora" |
|
1645 | msgstr "Dziennik administratora" | |
1647 |
|
1646 | |||
1648 | #: rhodecode/templates/admin/admin.html:10 |
|
1647 | #: rhodecode/templates/admin/admin.html:10 | |
1649 | #, fuzzy |
|
|||
1650 | msgid "journal filter..." |
|
1648 | msgid "journal filter..." | |
1651 |
msgstr "szybki |
|
1649 | msgstr "szybkie wyszukiwanie..." | |
1652 |
|
1650 | |||
1653 | #: rhodecode/templates/admin/admin.html:12 |
|
1651 | #: rhodecode/templates/admin/admin.html:12 | |
1654 | #: rhodecode/templates/journal/journal.html:11 |
|
1652 | #: rhodecode/templates/journal/journal.html:11 | |
1655 | #, fuzzy |
|
|||
1656 | msgid "filter" |
|
1653 | msgid "filter" | |
1657 |
msgstr " |
|
1654 | msgstr "filtr" | |
1658 |
|
1655 | |||
1659 | #: rhodecode/templates/admin/admin.html:13 |
|
1656 | #: rhodecode/templates/admin/admin.html:13 | |
1660 | #: rhodecode/templates/journal/journal.html:12 |
|
1657 | #: rhodecode/templates/journal/journal.html:12 | |
1661 | #, python-format |
|
1658 | #, python-format | |
1662 | msgid "%s entry" |
|
1659 | msgid "%s entry" | |
1663 | msgid_plural "%s entries" |
|
1660 | msgid_plural "%s entries" | |
1664 | msgstr[0] "" |
|
1661 | msgstr[0] "%s wejście" | |
1665 | msgstr[1] "" |
|
1662 | msgstr[1] "%s wejść" | |
1666 | msgstr[2] "" |
|
1663 | msgstr[2] "%s wejść" | |
1667 |
|
1664 | |||
1668 | #: rhodecode/templates/admin/admin_log.html:6 |
|
1665 | #: rhodecode/templates/admin/admin_log.html:6 | |
1669 | #: rhodecode/templates/admin/repos/repos.html:74 |
|
1666 | #: rhodecode/templates/admin/repos/repos.html:74 | |
@@ -1699,14 +1696,12 b' msgstr "Brak akcji"' | |||||
1699 |
|
1696 | |||
1700 | #: rhodecode/templates/admin/defaults/defaults.html:5 |
|
1697 | #: rhodecode/templates/admin/defaults/defaults.html:5 | |
1701 | #: rhodecode/templates/admin/defaults/defaults.html:25 |
|
1698 | #: rhodecode/templates/admin/defaults/defaults.html:25 | |
1702 | #, fuzzy |
|
|||
1703 | msgid "Repositories defaults" |
|
1699 | msgid "Repositories defaults" | |
1704 |
msgstr " |
|
1700 | msgstr "Repozytoria domyślne" | |
1705 |
|
1701 | |||
1706 | #: rhodecode/templates/admin/defaults/defaults.html:11 |
|
1702 | #: rhodecode/templates/admin/defaults/defaults.html:11 | |
1707 | #, fuzzy |
|
|||
1708 | msgid "Defaults" |
|
1703 | msgid "Defaults" | |
1709 |
msgstr " |
|
1704 | msgstr "Domyślne" | |
1710 |
|
1705 | |||
1711 | #: rhodecode/templates/admin/defaults/defaults.html:35 |
|
1706 | #: rhodecode/templates/admin/defaults/defaults.html:35 | |
1712 | #: rhodecode/templates/admin/repos/repo_add_base.html:38 |
|
1707 | #: rhodecode/templates/admin/repos/repo_add_base.html:38 | |
@@ -1719,12 +1714,8 b' msgstr "Typ"' | |||||
1719 | #: rhodecode/templates/admin/repos/repo_edit.html:89 |
|
1714 | #: rhodecode/templates/admin/repos/repo_edit.html:89 | |
1720 | #: rhodecode/templates/forks/fork.html:72 |
|
1715 | #: rhodecode/templates/forks/fork.html:72 | |
1721 | #: rhodecode/templates/settings/repo_settings.html:80 |
|
1716 | #: rhodecode/templates/settings/repo_settings.html:80 | |
1722 | msgid "" |
|
1717 | msgid "Private repositories are only visible to people explicitly added as collaborators." | |
1723 | "Private repositories are only visible to people explicitly added as " |
|
1718 | msgstr "Prywatne repozytoria są widoczne tylko dla osób bezpośrednio dodanych jako współpracownicy." | |
1724 | "collaborators." |
|
|||
1725 | msgstr "" |
|
|||
1726 | "Prywatne repozytoria są widoczne tylko dla osób bezpośrednio dodanych " |
|
|||
1727 | "jako współpracownicy." |
|
|||
1728 |
|
1719 | |||
1729 | #: rhodecode/templates/admin/defaults/defaults.html:55 |
|
1720 | #: rhodecode/templates/admin/defaults/defaults.html:55 | |
1730 | #: rhodecode/templates/admin/repos/repo_edit.html:94 |
|
1721 | #: rhodecode/templates/admin/repos/repo_edit.html:94 | |
@@ -1900,14 +1891,8 b' msgid "Anonymous access"' | |||||
1900 | msgstr "Dostęp anonimowy" |
|
1891 | msgstr "Dostęp anonimowy" | |
1901 |
|
1892 | |||
1902 | #: rhodecode/templates/admin/permissions/permissions.html:49 |
|
1893 | #: rhodecode/templates/admin/permissions/permissions.html:49 | |
1903 | msgid "" |
|
1894 | msgid "All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost" | |
1904 | "All default permissions on each repository will be reset to choosen " |
|
1895 | msgstr "Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie niestandardowe uprawnienia w repozytoriach zostaną utracone." | |
1905 | "permission, note that all custom default permission on repositories will " |
|
|||
1906 | "be lost" |
|
|||
1907 | msgstr "" |
|
|||
1908 | "Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. " |
|
|||
1909 | "Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie " |
|
|||
1910 | "niestandardowe uprawnienia w repozytoriach zostaną utracone." |
|
|||
1911 |
|
1896 | |||
1912 | #: rhodecode/templates/admin/permissions/permissions.html:50 |
|
1897 | #: rhodecode/templates/admin/permissions/permissions.html:50 | |
1913 | #: rhodecode/templates/admin/permissions/permissions.html:63 |
|
1898 | #: rhodecode/templates/admin/permissions/permissions.html:63 | |
@@ -1924,14 +1909,8 b' msgid "Repository group"' | |||||
1924 | msgstr "Repozytorium grupy" |
|
1909 | msgstr "Repozytorium grupy" | |
1925 |
|
1910 | |||
1926 | #: rhodecode/templates/admin/permissions/permissions.html:62 |
|
1911 | #: rhodecode/templates/admin/permissions/permissions.html:62 | |
1927 | msgid "" |
|
1912 | msgid "All default permissions on each repository group will be reset to choosen permission, note that all custom default permission on repositories group will be lost" | |
1928 | "All default permissions on each repository group will be reset to choosen" |
|
1913 | msgstr "Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie niestandardowe uprawnienia w repozytoriach zostaną utracone." | |
1929 | " permission, note that all custom default permission on repositories " |
|
|||
1930 | "group will be lost" |
|
|||
1931 | msgstr "" |
|
|||
1932 | "Wszystkie uprawnienia domyślne każdego repozytorium zostaną przywrócone. " |
|
|||
1933 | "Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie " |
|
|||
1934 | "niestandardowe uprawnienia w repozytoriach zostaną utracone." |
|
|||
1935 |
|
1914 | |||
1936 | #: rhodecode/templates/admin/permissions/permissions.html:69 |
|
1915 | #: rhodecode/templates/admin/permissions/permissions.html:69 | |
1937 | msgid "Registration" |
|
1916 | msgid "Registration" | |
@@ -2112,12 +2091,8 b' msgid "Confirm to invalidate repository ' | |||||
2112 | msgstr "Potwierdź unieważnienie pamięci podręcznej repozytorium" |
|
2091 | msgstr "Potwierdź unieważnienie pamięci podręcznej repozytorium" | |
2113 |
|
2092 | |||
2114 | #: rhodecode/templates/admin/repos/repo_edit.html:193 |
|
2093 | #: rhodecode/templates/admin/repos/repo_edit.html:193 | |
2115 | msgid "" |
|
2094 | msgid "Manually invalidate cache for this repository. On first access repository will be cached again" | |
2116 | "Manually invalidate cache for this repository. On first access repository" |
|
2095 | msgstr "Ręcznie unieważnienie cache dla tego repozytorium. Przy pierwszym dostępie do repozytorium zostanie dodany do bufora ponownie" | |
2117 | " will be cached again" |
|
|||
2118 | msgstr "" |
|
|||
2119 | "Ręcznie unieważnienie cache dla tego repozytorium. Przy pierwszym " |
|
|||
2120 | "dostępie do repozytorium zostanie dodany do bufora ponownie" |
|
|||
2121 |
|
2096 | |||
2122 | #: rhodecode/templates/admin/repos/repo_edit.html:198 |
|
2097 | #: rhodecode/templates/admin/repos/repo_edit.html:198 | |
2123 | msgid "List of cached values" |
|
2098 | msgid "List of cached values" | |
@@ -2125,12 +2100,11 b' msgstr "Lista buforowanych warto\xc5\x9bci"' | |||||
2125 |
|
2100 | |||
2126 | #: rhodecode/templates/admin/repos/repo_edit.html:201 |
|
2101 | #: rhodecode/templates/admin/repos/repo_edit.html:201 | |
2127 | msgid "Prefix" |
|
2102 | msgid "Prefix" | |
2128 | msgstr "" |
|
2103 | msgstr "Prefiks" | |
2129 |
|
2104 | |||
2130 | #: rhodecode/templates/admin/repos/repo_edit.html:202 |
|
2105 | #: rhodecode/templates/admin/repos/repo_edit.html:202 | |
2131 | #, fuzzy |
|
|||
2132 | msgid "Key" |
|
2106 | msgid "Key" | |
2133 |
msgstr "Klucz |
|
2107 | msgstr "Klucz" | |
2134 |
|
2108 | |||
2135 | #: rhodecode/templates/admin/repos/repo_edit.html:203 |
|
2109 | #: rhodecode/templates/admin/repos/repo_edit.html:203 | |
2136 | #: rhodecode/templates/admin/users/user_add.html:86 |
|
2110 | #: rhodecode/templates/admin/users/user_add.html:86 | |
@@ -2156,12 +2130,8 b' msgid "Add to public journal"' | |||||
2156 | msgstr "Dodaj do dziennika publicznego" |
|
2130 | msgstr "Dodaj do dziennika publicznego" | |
2157 |
|
2131 | |||
2158 | #: rhodecode/templates/admin/repos/repo_edit.html:231 |
|
2132 | #: rhodecode/templates/admin/repos/repo_edit.html:231 | |
2159 | msgid "" |
|
2133 | msgid "All actions made on this repository will be accessible to everyone in public journal" | |
2160 | "All actions made on this repository will be accessible to everyone in " |
|
2134 | msgstr "Wszystkie działania wykonywane na tym repozytorium będą dostępne dla wszystkich w dzienniku publicznym" | |
2161 | "public journal" |
|
|||
2162 | msgstr "" |
|
|||
2163 | "Wszystkie działania wykonywane na tym repozytorium będą dostępne dla " |
|
|||
2164 | "wszystkich w dzienniku publicznym" |
|
|||
2165 |
|
2135 | |||
2166 | #: rhodecode/templates/admin/repos/repo_edit.html:238 |
|
2136 | #: rhodecode/templates/admin/repos/repo_edit.html:238 | |
2167 | msgid "Locking" |
|
2137 | msgid "Locking" | |
@@ -2189,9 +2159,7 b' msgstr "Repozytorium nie jest zablokowan' | |||||
2189 |
|
2159 | |||
2190 | #: rhodecode/templates/admin/repos/repo_edit.html:252 |
|
2160 | #: rhodecode/templates/admin/repos/repo_edit.html:252 | |
2191 | msgid "Force locking on repository. Works only when anonymous access is disabled" |
|
2161 | msgid "Force locking on repository. Works only when anonymous access is disabled" | |
2192 | msgstr "" |
|
2162 | msgstr "Wymuś blokowanie na repozytorium. Działa tylko wtedy, gdy dostęp anonimowy jest wyłączony" | |
2193 | "Wymuś blokowanie na repozytorium. Działa tylko wtedy, gdy dostęp " |
|
|||
2194 | "anonimowy jest wyłączony" |
|
|||
2195 |
|
2163 | |||
2196 | #: rhodecode/templates/admin/repos/repo_edit.html:259 |
|
2164 | #: rhodecode/templates/admin/repos/repo_edit.html:259 | |
2197 | msgid "Set as fork of" |
|
2165 | msgid "Set as fork of" | |
@@ -2218,15 +2186,8 b' msgstr "Potwierd\xc5\xba, aby usun\xc4\x85\xc4\x87 repozytorium"' | |||||
2218 |
|
2186 | |||
2219 | #: rhodecode/templates/admin/repos/repo_edit.html:282 |
|
2187 | #: rhodecode/templates/admin/repos/repo_edit.html:282 | |
2220 | #: rhodecode/templates/settings/repo_settings.html:119 |
|
2188 | #: rhodecode/templates/settings/repo_settings.html:119 | |
2221 | #, fuzzy |
|
2189 | msgid "This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need fully delete it from file system please do it manually" | |
2222 | msgid "" |
|
2190 | msgstr "To repozytorium zostanie zmienione w sposób szczególny, żeby było niedostępne dla strony i systemów VCS. Jeśli chcesz całkowicie usunąć go z systemu plików prosimy zrobić to ręcznie" | |
2223 | "This repository will be renamed in a special way in order to be " |
|
|||
2224 | "unaccesible for RhodeCode and VCS systems. If you need fully delete it " |
|
|||
2225 | "from file system please do it manually" |
|
|||
2226 | msgstr "" |
|
|||
2227 | "To repozytorium zostanie zmienione w sposób szczególny, żeby było " |
|
|||
2228 | "niedostępne dla strony i systemów VCS. Jeśli chcesz całkowicie usunąć go " |
|
|||
2229 | "z systemu plików prosimy zrobić to ręcznie" |
|
|||
2230 |
|
2191 | |||
2231 | #: rhodecode/templates/admin/repos/repo_edit_perms.html:3 |
|
2192 | #: rhodecode/templates/admin/repos/repo_edit_perms.html:3 | |
2232 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3 |
|
2193 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3 | |
@@ -2295,14 +2256,14 b' msgid "Repositories administration"' | |||||
2295 | msgstr "Administracja repozytoriami" |
|
2256 | msgstr "Administracja repozytoriami" | |
2296 |
|
2257 | |||
2297 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:73 |
|
2258 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:73 | |
|
2259 | #, fuzzy | |||
2298 | msgid "apply to children" |
|
2260 | msgid "apply to children" | |
2299 | msgstr "" |
|
2261 | msgstr "dotyczy dzieci" | |
2300 |
|
2262 | |||
2301 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:74 |
|
2263 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:74 | |
2302 | msgid "" |
|
2264 | #, fuzzy | |
2303 | "Set or revoke permission to all children of that group, including " |
|
2265 | msgid "Set or revoke permission to all children of that group, including repositories and other groups" | |
2304 | "repositories and other groups" |
|
2266 | msgstr "Ustawia lub cofa uprawnienia do wszystkich dzieci z tej grupy, w tym repozytoria oraz innych grup" | |
2305 | msgstr "" |
|
|||
2306 |
|
2267 | |||
2307 | #: rhodecode/templates/admin/repos_groups/repos_groups.html:9 |
|
2268 | #: rhodecode/templates/admin/repos_groups/repos_groups.html:9 | |
2308 | #: rhodecode/templates/base/base.html:122 |
|
2269 | #: rhodecode/templates/base/base.html:122 | |
@@ -2320,7 +2281,8 b' msgstr ""' | |||||
2320 | #: rhodecode/templates/files/files_add.html:15 |
|
2281 | #: rhodecode/templates/files/files_add.html:15 | |
2321 | #: rhodecode/templates/files/files_edit.html:15 |
|
2282 | #: rhodecode/templates/files/files_edit.html:15 | |
2322 | #: rhodecode/templates/followers/followers.html:9 |
|
2283 | #: rhodecode/templates/followers/followers.html:9 | |
2323 |
#: rhodecode/templates/forks/fork.html:9 |
|
2284 | #: rhodecode/templates/forks/fork.html:9 | |
|
2285 | #: rhodecode/templates/forks/forks.html:9 | |||
2324 | #: rhodecode/templates/pullrequests/pullrequest.html:8 |
|
2286 | #: rhodecode/templates/pullrequests/pullrequest.html:8 | |
2325 | #: rhodecode/templates/pullrequests/pullrequest_show.html:8 |
|
2287 | #: rhodecode/templates/pullrequests/pullrequest_show.html:8 | |
2326 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:8 |
|
2288 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:8 | |
@@ -2370,12 +2332,8 b' msgid "edit repos group"' | |||||
2370 | msgstr "edytuj grupy repo" |
|
2332 | msgstr "edytuj grupy repo" | |
2371 |
|
2333 | |||
2372 | #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70 |
|
2334 | #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70 | |
2373 | msgid "" |
|
2335 | msgid "Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside" | |
2374 | "Enable lock-by-pulling on group. This option will be applied to all other" |
|
2336 | msgstr "Włącz blokowanie pulling przez grupy. Opcja ta będzie stosowana do wszystkich innych grup i repozytoriów wewnątrz" | |
2375 | " groups and repositories inside" |
|
|||
2376 | msgstr "" |
|
|||
2377 | "Włącz blokowanie pulling przez grupy. Opcja ta będzie stosowana do " |
|
|||
2378 | "wszystkich innych grup i repozytoriów wewnątrz" |
|
|||
2379 |
|
2337 | |||
2380 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5 |
|
2338 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5 | |
2381 | msgid "Repositories groups administration" |
|
2339 | msgid "Repositories groups administration" | |
@@ -2404,12 +2362,12 b' msgid "delete"' | |||||
2404 | msgstr "usuń" |
|
2362 | msgstr "usuń" | |
2405 |
|
2363 | |||
2406 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:55 |
|
2364 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:55 | |
2407 |
#, |
|
2365 | #, python-format | |
2408 | msgid "Confirm to delete this group: %s with %s repository" |
|
2366 | msgid "Confirm to delete this group: %s with %s repository" | |
2409 | msgid_plural "Confirm to delete this group: %s with %s repositories" |
|
2367 | msgid_plural "Confirm to delete this group: %s with %s repositories" | |
2410 |
msgstr[0] "Potwierd |
|
2368 | msgstr[0] "Potwierdź żeby usunąć grupę %s wraz z %s repozytorium" | |
2411 |
msgstr[1] "Potwierd |
|
2369 | msgstr[1] "Potwierdź żeby usunąć grupę %s wraz z %s repozytoriami" | |
2412 |
msgstr[2] "Potwierd |
|
2370 | msgstr[2] "Potwierdź żeby usunąć grupę %s wraz z %s repozytoriami" | |
2413 |
|
2371 | |||
2414 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:63 |
|
2372 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:63 | |
2415 | msgid "There are no repositories groups yet" |
|
2373 | msgid "There are no repositories groups yet" | |
@@ -2451,26 +2409,16 b' msgid "rescan option"' | |||||
2451 | msgstr "ponowne skanowanie opcji" |
|
2409 | msgstr "ponowne skanowanie opcji" | |
2452 |
|
2410 | |||
2453 | #: rhodecode/templates/admin/settings/settings.html:38 |
|
2411 | #: rhodecode/templates/admin/settings/settings.html:38 | |
2454 | msgid "" |
|
2412 | msgid "In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it." | |
2455 | "In case a repository was deleted from filesystem and there are leftovers " |
|
2413 | msgstr "W przypadku repozytoriów zostaną usunięte systemy plików i jeśli są pozostałości w bazie danych to ta opcja sprawdzi ją oraz przeskanuje, a następnie usunie je z bazy danych." | |
2456 | "in the database check this option to scan obsolete data in database and " |
|
|||
2457 | "remove it." |
|
|||
2458 | msgstr "" |
|
|||
2459 | "W przypadku repozytoriów zostaną usunięte systemy plików i jeśli są " |
|
|||
2460 | "pozostałości w bazie danych to ta opcja sprawdzi ją oraz przeskanuje, a " |
|
|||
2461 | "następnie usunie je z bazy danych." |
|
|||
2462 |
|
2414 | |||
2463 | #: rhodecode/templates/admin/settings/settings.html:39 |
|
2415 | #: rhodecode/templates/admin/settings/settings.html:39 | |
2464 | msgid "destroy old data" |
|
2416 | msgid "destroy old data" | |
2465 | msgstr "zniszcz stare dane" |
|
2417 | msgstr "zniszcz stare dane" | |
2466 |
|
2418 | |||
2467 | #: rhodecode/templates/admin/settings/settings.html:41 |
|
2419 | #: rhodecode/templates/admin/settings/settings.html:41 | |
2468 | msgid "" |
|
2420 | msgid "Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked " | |
2469 | "Rescan repositories location for new repositories. Also deletes obsolete " |
|
2421 | msgstr "Skanowanie ponowne lokalizacji dla nowych repozytoriów. Usuwa również nieaktualne jeśli została zaznaczona flaga `zniszcz` do sprawdzana" | |
2470 | "if `destroy` flag is checked " |
|
|||
2471 | msgstr "" |
|
|||
2472 | "Skanowanie ponowne lokalizacji dla nowych repozytoriów. Usuwa również " |
|
|||
2473 | "nieaktualne jeśli została zaznaczona flaga `zniszcz` do sprawdzana" |
|
|||
2474 |
|
2422 | |||
2475 | #: rhodecode/templates/admin/settings/settings.html:46 |
|
2423 | #: rhodecode/templates/admin/settings/settings.html:46 | |
2476 | msgid "Rescan repositories" |
|
2424 | msgid "Rescan repositories" | |
@@ -2519,9 +2467,8 b' msgid "Visualisation settings"' | |||||
2519 | msgstr "Ustawienia wizualizacji" |
|
2467 | msgstr "Ustawienia wizualizacji" | |
2520 |
|
2468 | |||
2521 | #: rhodecode/templates/admin/settings/settings.html:127 |
|
2469 | #: rhodecode/templates/admin/settings/settings.html:127 | |
2522 | #, fuzzy |
|
|||
2523 | msgid "General" |
|
2470 | msgid "General" | |
2524 |
msgstr " |
|
2471 | msgstr "Główne" | |
2525 |
|
2472 | |||
2526 | #: rhodecode/templates/admin/settings/settings.html:132 |
|
2473 | #: rhodecode/templates/admin/settings/settings.html:132 | |
2527 | msgid "Use lightweight dashboard" |
|
2474 | msgid "Use lightweight dashboard" | |
@@ -2560,12 +2507,8 b' msgid "require ssl for vcs operations"' | |||||
2560 | msgstr "wymagaj ssl dla operacji vcs" |
|
2507 | msgstr "wymagaj ssl dla operacji vcs" | |
2561 |
|
2508 | |||
2562 | #: rhodecode/templates/admin/settings/settings.html:203 |
|
2509 | #: rhodecode/templates/admin/settings/settings.html:203 | |
2563 | msgid "" |
|
2510 | msgid "RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable" | |
2564 | "RhodeCode will require SSL for pushing or pulling. If SSL is missing it " |
|
2511 | msgstr "RhodeCode wymaga SSL do wysłania zmian lub pobierania. Jeśli brakuje SSL zwróci błąd HTTP 406: Not Acceptable" | |
2565 | "will return HTTP Error 406: Not Acceptable" |
|
|||
2566 | msgstr "" |
|
|||
2567 | "RhodeCode wymaga SSL do wysłania zmian lub pobierania. Jeśli brakuje SSL " |
|
|||
2568 | "zwróci błąd HTTP 406: Not Acceptable" |
|
|||
2569 |
|
2512 | |||
2570 | #: rhodecode/templates/admin/settings/settings.html:209 |
|
2513 | #: rhodecode/templates/admin/settings/settings.html:209 | |
2571 | msgid "Hooks" |
|
2514 | msgid "Hooks" | |
@@ -2604,26 +2547,16 b' msgid "hgsubversion extensions"' | |||||
2604 | msgstr "rozszerzenia hgsubversion" |
|
2547 | msgstr "rozszerzenia hgsubversion" | |
2605 |
|
2548 | |||
2606 | #: rhodecode/templates/admin/settings/settings.html:246 |
|
2549 | #: rhodecode/templates/admin/settings/settings.html:246 | |
2607 | msgid "" |
|
2550 | msgid "Requires hgsubversion library installed. Allows clonning from svn remote locations" | |
2608 | "Requires hgsubversion library installed. Allows clonning from svn remote " |
|
2551 | msgstr "Wymaga biblioteki hgsubversion zainstalowanej. Umożliwia klonowanie z zdalnych lokalizacji svn" | |
2609 | "locations" |
|
|||
2610 | msgstr "" |
|
|||
2611 | "Wymaga biblioteki hgsubversion zainstalowanej. Umożliwia klonowanie z " |
|
|||
2612 | "zdalnych lokalizacji svn" |
|
|||
2613 |
|
2552 | |||
2614 | #: rhodecode/templates/admin/settings/settings.html:256 |
|
2553 | #: rhodecode/templates/admin/settings/settings.html:256 | |
2615 | msgid "Repositories location" |
|
2554 | msgid "Repositories location" | |
2616 | msgstr "Położenie repozytorium" |
|
2555 | msgstr "Położenie repozytorium" | |
2617 |
|
2556 | |||
2618 | #: rhodecode/templates/admin/settings/settings.html:261 |
|
2557 | #: rhodecode/templates/admin/settings/settings.html:261 | |
2619 | msgid "" |
|
2558 | msgid "This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock." | |
2620 | "This a crucial application setting. If you are really sure you need to " |
|
2559 | msgstr "To kluczowe ustawienia aplikacji. Jeśli jesteś pewny, że chcesz to zmienić, należy ponownie uruchomić aplikację w celu zaktualizowania lokalizacji. Kliknij tą etykietę, żeby odblokować." | |
2621 | "change this, you must restart application in order to make this setting " |
|
|||
2622 | "take effect. Click this label to unlock." |
|
|||
2623 | msgstr "" |
|
|||
2624 | "To kluczowe ustawienia aplikacji. Jeśli jesteś pewny, że chcesz to " |
|
|||
2625 | "zmienić, należy ponownie uruchomić aplikację w celu zaktualizowania " |
|
|||
2626 | "lokalizacji. Kliknij tą etykietę, żeby odblokować." |
|
|||
2627 |
|
2560 | |||
2628 | #: rhodecode/templates/admin/settings/settings.html:262 |
|
2561 | #: rhodecode/templates/admin/settings/settings.html:262 | |
2629 | #: rhodecode/templates/base/base.html:221 |
|
2562 | #: rhodecode/templates/base/base.html:221 | |
@@ -2631,12 +2564,8 b' msgid "unlock"' | |||||
2631 | msgstr "odblokowany" |
|
2564 | msgstr "odblokowany" | |
2632 |
|
2565 | |||
2633 | #: rhodecode/templates/admin/settings/settings.html:263 |
|
2566 | #: rhodecode/templates/admin/settings/settings.html:263 | |
2634 | msgid "" |
|
2567 | msgid "Location where repositories are stored. After changing this value a restart, and rescan is required" | |
2635 | "Location where repositories are stored. After changing this value a " |
|
2568 | msgstr "Miejsce, w którym przechowywane są repozytoria. Po zmianie tej wartości jest wymagany restart i ponowne skanowanie" | |
2636 | "restart, and rescan is required" |
|
|||
2637 | msgstr "" |
|
|||
2638 | "Miejsce, w którym przechowywane są repozytoria. Po zmianie tej wartości " |
|
|||
2639 | "jest wymagany restart i ponowne skanowanie" |
|
|||
2640 |
|
2569 | |||
2641 | #: rhodecode/templates/admin/settings/settings.html:283 |
|
2570 | #: rhodecode/templates/admin/settings/settings.html:283 | |
2642 | msgid "Test Email" |
|
2571 | msgid "Test Email" | |
@@ -2716,12 +2645,8 b' msgstr "Dziedzicz\xc4\x85 uprawnienia domy\xc5\x9blne"' | |||||
2716 | #: rhodecode/templates/admin/users/user_edit.html:156 |
|
2645 | #: rhodecode/templates/admin/users/user_edit.html:156 | |
2717 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:113 |
|
2646 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:113 | |
2718 | #, python-format |
|
2647 | #, python-format | |
2719 | msgid "" |
|
2648 | msgid "Select to inherit permissions from %s settings. With this selected below options does not have any action" | |
2720 | "Select to inherit permissions from %s settings. With this selected below " |
|
2649 | msgstr "Zaznacz, żeby dziedziczyć uprawnienia z %s ustawień. Po wybraniu tej opcji, poniżej nie ma żadnych działań" | |
2721 | "options does not have any action" |
|
|||
2722 | msgstr "" |
|
|||
2723 | "Zaznacz, żeby dziedziczyć uprawnienia z %s ustawień. Po wybraniu tej " |
|
|||
2724 | "opcji, poniżej nie ma żadnych działań" |
|
|||
2725 |
|
2650 | |||
2726 | #: rhodecode/templates/admin/users/user_edit.html:162 |
|
2651 | #: rhodecode/templates/admin/users/user_edit.html:162 | |
2727 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:119 |
|
2652 | #: rhodecode/templates/admin/users_groups/users_group_edit.html:119 | |
@@ -3010,7 +2935,8 b' msgid "Products"' | |||||
3010 | msgstr "Produkty" |
|
2935 | msgstr "Produkty" | |
3011 |
|
2936 | |||
3012 | #: rhodecode/templates/base/base.html:152 |
|
2937 | #: rhodecode/templates/base/base.html:152 | |
3013 |
#: rhodecode/templates/base/base.html:182 |
|
2938 | #: rhodecode/templates/base/base.html:182 | |
|
2939 | #: rhodecode/templates/base/root.html:47 | |||
3014 | msgid "loading..." |
|
2940 | msgid "loading..." | |
3015 | msgstr "wczytywanie..." |
|
2941 | msgstr "wczytywanie..." | |
3016 |
|
2942 | |||
@@ -3064,7 +2990,8 b' msgstr "ustawienia repozytorium"' | |||||
3064 | msgid "fork" |
|
2990 | msgid "fork" | |
3065 | msgstr "gałąż" |
|
2991 | msgstr "gałąż" | |
3066 |
|
2992 | |||
3067 |
#: rhodecode/templates/base/base.html:212 |
|
2993 | #: rhodecode/templates/base/base.html:212 | |
|
2994 | #: rhodecode/templates/base/root.html:50 | |||
3068 | #: rhodecode/templates/changelog/changelog.html:43 |
|
2995 | #: rhodecode/templates/changelog/changelog.html:43 | |
3069 | msgid "Open new pull request" |
|
2996 | msgid "Open new pull request" | |
3070 | msgstr "Otwórz nową prośbę o połączenie gałęzi" |
|
2997 | msgstr "Otwórz nową prośbę o połączenie gałęzi" | |
@@ -3095,7 +3022,6 b' msgid "permissions"' | |||||
3095 | msgstr "uprawnienia" |
|
3022 | msgstr "uprawnienia" | |
3096 |
|
3023 | |||
3097 | #: rhodecode/templates/base/base.html:239 |
|
3024 | #: rhodecode/templates/base/base.html:239 | |
3098 | #, fuzzy |
|
|||
3099 | msgid "defaults" |
|
3025 | msgid "defaults" | |
3100 | msgstr "domyślne" |
|
3026 | msgstr "domyślne" | |
3101 |
|
3027 | |||
@@ -3215,9 +3141,8 b' msgid "compare fork with %s"' | |||||
3215 | msgstr "porównaj gałęzie %s" |
|
3141 | msgstr "porównaj gałęzie %s" | |
3216 |
|
3142 | |||
3217 | #: rhodecode/templates/changelog/changelog.html:40 |
|
3143 | #: rhodecode/templates/changelog/changelog.html:40 | |
3218 | #, fuzzy |
|
|||
3219 | msgid "Compare fork with parent" |
|
3144 | msgid "Compare fork with parent" | |
3220 |
msgstr "porównaj |
|
3145 | msgstr "porównaj gałąź w rodzicem" | |
3221 |
|
3146 | |||
3222 | #: rhodecode/templates/changelog/changelog.html:49 |
|
3147 | #: rhodecode/templates/changelog/changelog.html:49 | |
3223 | msgid "Show" |
|
3148 | msgid "Show" | |
@@ -3319,8 +3244,9 b' msgid "Changeset"' | |||||
3319 | msgstr "Grupy zmian" |
|
3244 | msgstr "Grupy zmian" | |
3320 |
|
3245 | |||
3321 | #: rhodecode/templates/changeset/changeset.html:52 |
|
3246 | #: rhodecode/templates/changeset/changeset.html:52 | |
|
3247 | #, fuzzy | |||
3322 | msgid "No children" |
|
3248 | msgid "No children" | |
3323 | msgstr "" |
|
3249 | msgstr "Brak dzieci" | |
3324 |
|
3250 | |||
3325 | #: rhodecode/templates/changeset/changeset.html:70 |
|
3251 | #: rhodecode/templates/changeset/changeset.html:70 | |
3326 | #: rhodecode/templates/changeset/diff_block.html:20 |
|
3252 | #: rhodecode/templates/changeset/diff_block.html:20 | |
@@ -3357,22 +3283,22 b' msgstr[2] "(%d linii)"' | |||||
3357 | #: rhodecode/templates/changeset/changeset.html:122 |
|
3283 | #: rhodecode/templates/changeset/changeset.html:122 | |
3358 | #: rhodecode/templates/compare/compare_diff.html:44 |
|
3284 | #: rhodecode/templates/compare/compare_diff.html:44 | |
3359 | #: rhodecode/templates/pullrequests/pullrequest_show.html:76 |
|
3285 | #: rhodecode/templates/pullrequests/pullrequest_show.html:76 | |
3360 |
#, |
|
3286 | #, python-format | |
3361 | msgid "%s file changed" |
|
3287 | msgid "%s file changed" | |
3362 | msgid_plural "%s files changed" |
|
3288 | msgid_plural "%s files changed" | |
3363 | msgstr[0] "%s plik zmieniony" |
|
3289 | msgstr[0] "%s plik został zmieniony" | |
3364 |
msgstr[1] "%s plik |
|
3290 | msgstr[1] "%s pliki zostały zmienione" | |
3365 | msgstr[2] "%s plików zmienionych" |
|
3291 | msgstr[2] "%s plików zostało zmienionych" | |
3366 |
|
3292 | |||
3367 | #: rhodecode/templates/changeset/changeset.html:124 |
|
3293 | #: rhodecode/templates/changeset/changeset.html:124 | |
3368 | #: rhodecode/templates/compare/compare_diff.html:46 |
|
3294 | #: rhodecode/templates/compare/compare_diff.html:46 | |
3369 | #: rhodecode/templates/pullrequests/pullrequest_show.html:78 |
|
3295 | #: rhodecode/templates/pullrequests/pullrequest_show.html:78 | |
3370 |
#, |
|
3296 | #, python-format | |
3371 | msgid "%s file changed with %s insertions and %s deletions" |
|
3297 | msgid "%s file changed with %s insertions and %s deletions" | |
3372 | msgid_plural "%s files changed with %s insertions and %s deletions" |
|
3298 | msgid_plural "%s files changed with %s insertions and %s deletions" | |
3373 |
msgstr[0] "%s plik zmieniony z %s in |
|
3299 | msgstr[0] "%s plik został zmieniony z %s inercjami i %s usunięciami" | |
3374 |
msgstr[1] "%s plików zmienionych z %s in |
|
3300 | msgstr[1] "%s plików zostało zmienionych z %s inercjami i %s usunięciami" | |
3375 |
msgstr[2] "%s plików zmienionych z %s in |
|
3301 | msgstr[2] "%s plików zostało zmienionych z %s inercjami i %s usunięciami" | |
3376 |
|
3302 | |||
3377 | #: rhodecode/templates/changeset/changeset_file_comment.html:42 |
|
3303 | #: rhodecode/templates/changeset/changeset_file_comment.html:42 | |
3378 | msgid "Submitting..." |
|
3304 | msgid "Submitting..." | |
@@ -3391,9 +3317,7 b' msgstr "Komentarze analizowane za pomoc\xc4\x85 %s sk\xc5\x82adni od %s wsparcia."' | |||||
3391 | #: rhodecode/templates/changeset/changeset_file_comment.html:48 |
|
3317 | #: rhodecode/templates/changeset/changeset_file_comment.html:48 | |
3392 | #: rhodecode/templates/changeset/changeset_file_comment.html:123 |
|
3318 | #: rhodecode/templates/changeset/changeset_file_comment.html:123 | |
3393 | msgid "Use @username inside this text to send notification to this RhodeCode user" |
|
3319 | msgid "Use @username inside this text to send notification to this RhodeCode user" | |
3394 | msgstr "" |
|
3320 | msgstr "Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do użytkownika strony" | |
3395 | "Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do " |
|
|||
3396 | "użytkownika strony" |
|
|||
3397 |
|
3321 | |||
3398 | #: rhodecode/templates/changeset/changeset_file_comment.html:59 |
|
3322 | #: rhodecode/templates/changeset/changeset_file_comment.html:59 | |
3399 | #: rhodecode/templates/changeset/changeset_file_comment.html:143 |
|
3323 | #: rhodecode/templates/changeset/changeset_file_comment.html:143 | |
@@ -3440,9 +3364,8 b' msgid "Compare View"' | |||||
3440 | msgstr "Wyświetl Porównanie" |
|
3364 | msgstr "Wyświetl Porównanie" | |
3441 |
|
3365 | |||
3442 | #: rhodecode/templates/changeset/changeset_range.html:29 |
|
3366 | #: rhodecode/templates/changeset/changeset_range.html:29 | |
3443 | #, fuzzy |
|
|||
3444 | msgid "Show combined compare" |
|
3367 | msgid "Show combined compare" | |
3445 |
msgstr " |
|
3368 | msgstr "Pokaż połączone porównaj" | |
3446 |
|
3369 | |||
3447 | #: rhodecode/templates/changeset/changeset_range.html:54 |
|
3370 | #: rhodecode/templates/changeset/changeset_range.html:54 | |
3448 | msgid "Files affected" |
|
3371 | msgid "Files affected" | |
@@ -3462,18 +3385,17 b' msgstr "Brak zestawienia zmian"' | |||||
3462 |
|
3385 | |||
3463 | #: rhodecode/templates/compare/compare_diff.html:37 |
|
3386 | #: rhodecode/templates/compare/compare_diff.html:37 | |
3464 | #: rhodecode/templates/pullrequests/pullrequest_show.html:69 |
|
3387 | #: rhodecode/templates/pullrequests/pullrequest_show.html:69 | |
3465 |
#, |
|
3388 | #, python-format | |
3466 | msgid "Showing %s commit" |
|
3389 | msgid "Showing %s commit" | |
3467 | msgid_plural "Showing %s commits" |
|
3390 | msgid_plural "Showing %s commits" | |
3468 |
msgstr[0] " |
|
3391 | msgstr[0] "Pokaż %s komentarz" | |
3469 |
msgstr[1] " |
|
3392 | msgstr[1] "Pokaż %s komentarze" | |
3470 |
msgstr[2] " |
|
3393 | msgstr[2] "Pokaż %s komentarze" | |
3471 |
|
3394 | |||
3472 | #: rhodecode/templates/compare/compare_diff.html:52 |
|
3395 | #: rhodecode/templates/compare/compare_diff.html:52 | |
3473 | #: rhodecode/templates/pullrequests/pullrequest_show.html:84 |
|
3396 | #: rhodecode/templates/pullrequests/pullrequest_show.html:84 | |
3474 | #, fuzzy |
|
|||
3475 | msgid "No files" |
|
3397 | msgid "No files" | |
3476 |
msgstr " |
|
3398 | msgstr "Brak plików" | |
3477 |
|
3399 | |||
3478 | #: rhodecode/templates/data_table/_dt_elements.html:39 |
|
3400 | #: rhodecode/templates/data_table/_dt_elements.html:39 | |
3479 | #: rhodecode/templates/data_table/_dt_elements.html:41 |
|
3401 | #: rhodecode/templates/data_table/_dt_elements.html:41 | |
@@ -3527,42 +3449,37 b' msgid "Confirm to delete this user: %s"' | |||||
3527 | msgstr "Potwierdź usunięcie tego użytkownika: %s" |
|
3449 | msgstr "Potwierdź usunięcie tego użytkownika: %s" | |
3528 |
|
3450 | |||
3529 | #: rhodecode/templates/email_templates/changeset_comment.html:10 |
|
3451 | #: rhodecode/templates/email_templates/changeset_comment.html:10 | |
3530 | #, fuzzy |
|
|||
3531 | msgid "New status$" |
|
3452 | msgid "New status$" | |
3532 |
msgstr " |
|
3453 | msgstr "Nowy status$" | |
3533 |
|
3454 | |||
3534 | #: rhodecode/templates/email_templates/main.html:8 |
|
3455 | #: rhodecode/templates/email_templates/main.html:8 | |
3535 | #, fuzzy |
|
|||
3536 | msgid "This is a notification from RhodeCode." |
|
3456 | msgid "This is a notification from RhodeCode." | |
3537 | msgstr "To jest powiadomienie z strony" |
|
3457 | msgstr "To jest powiadomienie z strony" | |
3538 |
|
3458 | |||
3539 | #: rhodecode/templates/email_templates/password_reset.html:4 |
|
3459 | #: rhodecode/templates/email_templates/password_reset.html:4 | |
3540 | msgid "Hello" |
|
3460 | msgid "Hello" | |
3541 | msgstr "" |
|
3461 | msgstr "Witaj" | |
3542 |
|
3462 | |||
3543 | #: rhodecode/templates/email_templates/password_reset.html:6 |
|
3463 | #: rhodecode/templates/email_templates/password_reset.html:6 | |
3544 | msgid "We received a request to create a new password for your account." |
|
3464 | msgid "We received a request to create a new password for your account." | |
3545 | msgstr "" |
|
3465 | msgstr "Otrzymaliśmy prośbę o utworzenie nowego hasła do twojego konta." | |
3546 |
|
3466 | |||
3547 | #: rhodecode/templates/email_templates/password_reset.html:8 |
|
3467 | #: rhodecode/templates/email_templates/password_reset.html:8 | |
3548 | msgid "You can generate it by clicking following URL" |
|
3468 | msgid "You can generate it by clicking following URL" | |
3549 | msgstr "" |
|
3469 | msgstr "Możesz wygenerować nowe hasło klikając w link URL poniżej:" | |
3550 |
|
3470 | |||
3551 | #: rhodecode/templates/email_templates/password_reset.html:12 |
|
3471 | #: rhodecode/templates/email_templates/password_reset.html:12 | |
3552 | msgid "If you didn't request new password please ignore this email." |
|
3472 | msgid "If you didn't request new password please ignore this email." | |
3553 | msgstr "" |
|
3473 | msgstr "Jeśli nie chcesz wygenerować nowego hasła to zignoruj tą wiadomość." | |
3554 |
|
3474 | |||
3555 | #: rhodecode/templates/email_templates/pull_request.html:4 |
|
3475 | #: rhodecode/templates/email_templates/pull_request.html:4 | |
3556 | #, python-format |
|
3476 | #, python-format | |
3557 | msgid "" |
|
3477 | msgid "User %s opened pull request for repository %s and wants you to review changes." | |
3558 | "User %s opened pull request for repository %s and wants you to review " |
|
3478 | msgstr "Użytkownik %s zgłosił wniosek połączenia w repozytorium %s i chce żeby sprawdzić zmiany." | |
3559 | "changes." |
|
|||
3560 | msgstr "" |
|
|||
3561 |
|
3479 | |||
3562 | #: rhodecode/templates/email_templates/pull_request.html:5 |
|
3480 | #: rhodecode/templates/email_templates/pull_request.html:5 | |
3563 | #, fuzzy |
|
|||
3564 | msgid "title" |
|
3481 | msgid "title" | |
3565 |
msgstr " |
|
3482 | msgstr "tytuł" | |
3566 |
|
3483 | |||
3567 | #: rhodecode/templates/email_templates/pull_request.html:6 |
|
3484 | #: rhodecode/templates/email_templates/pull_request.html:6 | |
3568 | #: rhodecode/templates/pullrequests/pullrequest.html:115 |
|
3485 | #: rhodecode/templates/pullrequests/pullrequest.html:115 | |
@@ -3571,37 +3488,32 b' msgstr "opis"' | |||||
3571 |
|
3488 | |||
3572 | #: rhodecode/templates/email_templates/pull_request.html:11 |
|
3489 | #: rhodecode/templates/email_templates/pull_request.html:11 | |
3573 | msgid "revisions for reviewing" |
|
3490 | msgid "revisions for reviewing" | |
3574 | msgstr "" |
|
3491 | msgstr "korekty dotyczące rewizji" | |
3575 |
|
3492 | |||
3576 | #: rhodecode/templates/email_templates/pull_request.html:18 |
|
3493 | #: rhodecode/templates/email_templates/pull_request.html:18 | |
3577 | #, fuzzy |
|
|||
3578 | msgid "View this pull request here" |
|
3494 | msgid "View this pull request here" | |
3579 | msgstr "Pokarz wszystkie zmiany" |
|
3495 | msgstr "Wyświetl prośby pobrania tutaj" | |
3580 |
|
3496 | |||
3581 | #: rhodecode/templates/email_templates/pull_request_comment.html:4 |
|
3497 | #: rhodecode/templates/email_templates/pull_request_comment.html:4 | |
3582 |
#, |
|
3498 | #, python-format | |
3583 | msgid "User %s commented on pull request #%s for repository %s" |
|
3499 | msgid "User %s commented on pull request #%s for repository %s" | |
3584 | msgstr "" |
|
3500 | msgstr "Użytkownik %s skomentował wniosek o połączenie gałęzi #%s dla repozytorium %s" | |
3585 | "Użytkownik %s skomentował wniosek o połączenie gałęzi #%s dla " |
|
|||
3586 | "repozytorium %s" |
|
|||
3587 |
|
3501 | |||
3588 | #: rhodecode/templates/email_templates/pull_request_comment.html:10 |
|
3502 | #: rhodecode/templates/email_templates/pull_request_comment.html:10 | |
3589 | #, fuzzy |
|
|||
3590 | msgid "New status" |
|
3503 | msgid "New status" | |
3591 |
msgstr " |
|
3504 | msgstr "Nowy status" | |
3592 |
|
3505 | |||
3593 | #: rhodecode/templates/email_templates/pull_request_comment.html:14 |
|
3506 | #: rhodecode/templates/email_templates/pull_request_comment.html:14 | |
3594 | msgid "View this comment here" |
|
3507 | msgid "View this comment here" | |
3595 | msgstr "" |
|
3508 | msgstr "Zobacz ten komentarz tutaj" | |
3596 |
|
3509 | |||
3597 | #: rhodecode/templates/email_templates/registration.html:4 |
|
3510 | #: rhodecode/templates/email_templates/registration.html:4 | |
3598 | #, fuzzy |
|
|||
3599 | msgid "A new user have registered in RhodeCode" |
|
3511 | msgid "A new user have registered in RhodeCode" | |
3600 |
msgstr " |
|
3512 | msgstr "Nowy użytkownik został zarejestrowany na stronie" | |
3601 |
|
3513 | |||
3602 | #: rhodecode/templates/email_templates/registration.html:9 |
|
3514 | #: rhodecode/templates/email_templates/registration.html:9 | |
3603 | msgid "View this user here" |
|
3515 | msgid "View this user here" | |
3604 | msgstr "" |
|
3516 | msgstr "Zobacz tego użytkownika tutaj" | |
3605 |
|
3517 | |||
3606 | #: rhodecode/templates/errors/error_document.html:46 |
|
3518 | #: rhodecode/templates/errors/error_document.html:46 | |
3607 | #, python-format |
|
3519 | #, python-format | |
@@ -3771,9 +3683,8 b' msgid "show at revision"' | |||||
3771 | msgstr "wskaż zmiany" |
|
3683 | msgstr "wskaż zmiany" | |
3772 |
|
3684 | |||
3773 | #: rhodecode/templates/files/files_history_box.html:11 |
|
3685 | #: rhodecode/templates/files/files_history_box.html:11 | |
3774 | #, fuzzy |
|
|||
3775 | msgid "show full history" |
|
3686 | msgid "show full history" | |
3776 | msgstr "Wczytywanie listy plików..." |
|
3687 | msgstr "pokaż pełną historię" | |
3777 |
|
3688 | |||
3778 | #: rhodecode/templates/files/files_history_box.html:16 |
|
3689 | #: rhodecode/templates/files/files_history_box.html:16 | |
3779 | #, python-format |
|
3690 | #, python-format | |
@@ -3784,9 +3695,8 b' msgstr[1] "%s autorzy"' | |||||
3784 | msgstr[2] "%s autorzy" |
|
3695 | msgstr[2] "%s autorzy" | |
3785 |
|
3696 | |||
3786 | #: rhodecode/templates/files/files_source.html:6 |
|
3697 | #: rhodecode/templates/files/files_source.html:6 | |
3787 | #, fuzzy |
|
|||
3788 | msgid "Load file history" |
|
3698 | msgid "Load file history" | |
3789 | msgstr "Wczytywanie listy plików..." |
|
3699 | msgstr "Załaduj historię pliku" | |
3790 |
|
3700 | |||
3791 | #: rhodecode/templates/files/files_source.html:21 |
|
3701 | #: rhodecode/templates/files/files_source.html:21 | |
3792 | msgid "show source" |
|
3702 | msgid "show source" | |
@@ -4020,9 +3930,8 b' msgid "Compare view"' | |||||
4020 | msgstr "Wyświetl porównanie" |
|
3930 | msgstr "Wyświetl porównanie" | |
4021 |
|
3931 | |||
4022 | #: rhodecode/templates/pullrequests/pullrequest_show.html:112 |
|
3932 | #: rhodecode/templates/pullrequests/pullrequest_show.html:112 | |
4023 | #, fuzzy |
|
|||
4024 | msgid "reviewer" |
|
3933 | msgid "reviewer" | |
4025 |
msgstr " |
|
3934 | msgstr "recenzent" | |
4026 |
|
3935 | |||
4027 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4 |
|
3936 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4 | |
4028 | msgid "all pull requests" |
|
3937 | msgid "all pull requests" | |
@@ -4089,14 +3998,12 b' msgid "%s Settings"' | |||||
4089 | msgstr "Ustawienia %s" |
|
3998 | msgstr "Ustawienia %s" | |
4090 |
|
3999 | |||
4091 | #: rhodecode/templates/settings/repo_settings.html:102 |
|
4000 | #: rhodecode/templates/settings/repo_settings.html:102 | |
4092 | #, fuzzy |
|
|||
4093 | msgid "Delete repository" |
|
4001 | msgid "Delete repository" | |
4094 |
msgstr " |
|
4002 | msgstr "Usuń repozytorium" | |
4095 |
|
4003 | |||
4096 | #: rhodecode/templates/settings/repo_settings.html:109 |
|
4004 | #: rhodecode/templates/settings/repo_settings.html:109 | |
4097 | #, fuzzy |
|
|||
4098 | msgid "Remove repo" |
|
4005 | msgid "Remove repo" | |
4099 |
msgstr " |
|
4006 | msgstr "Usuń repo" | |
4100 |
|
4007 | |||
4101 | #: rhodecode/templates/shortlog/shortlog.html:5 |
|
4008 | #: rhodecode/templates/shortlog/shortlog.html:5 | |
4102 | #, python-format |
|
4009 | #, python-format | |
@@ -4296,9 +4203,8 b' msgid "%s Tags"' | |||||
4296 | msgstr "Etykiety pliku %s" |
|
4203 | msgstr "Etykiety pliku %s" | |
4297 |
|
4204 | |||
4298 | #: rhodecode/templates/tags/tags.html:29 |
|
4205 | #: rhodecode/templates/tags/tags.html:29 | |
4299 | #, fuzzy |
|
|||
4300 | msgid "Compare tags" |
|
4206 | msgid "Compare tags" | |
4301 |
msgstr " |
|
4207 | msgstr "Porównaj tagi" | |
4302 |
|
4208 | |||
4303 | #~ msgid "" |
|
4209 | #~ msgid "" | |
4304 | #~ "%s repository is not mapped to db" |
|
4210 | #~ "%s repository is not mapped to db" | |
@@ -4325,4 +4231,3 b' msgstr "por\xc3\xb3wnanie"' | |||||
4325 | #~ "zmienione w systemie plików proszę " |
|
4231 | #~ "zmienione w systemie plików proszę " | |
4326 | #~ "uruchomić aplikację ponownie, aby ponownie " |
|
4232 | #~ "uruchomić aplikację ponownie, aby ponownie " | |
4327 | #~ "przeskanować repozytoria" |
|
4233 | #~ "przeskanować repozytoria" | |
4328 |
|
1 | NO CONTENT: modified file, binary diff hidden |
|
NO CONTENT: modified file, binary diff hidden |
@@ -8,15 +8,16 b' msgid ""' | |||||
8 | msgstr "" |
|
8 | msgstr "" | |
9 | "Project-Id-Version: RhodeCode 1.4.4\n" |
|
9 | "Project-Id-Version: RhodeCode 1.4.4\n" | |
10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" |
|
10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | |
11 |
"POT-Creation-Date: 2012-12-14 |
|
11 | "POT-Creation-Date: 2012-12-14 12:53+0800\n" | |
12 |
"PO-Revision-Date: 2012-1 |
|
12 | "PO-Revision-Date: 2012-12-14 12:57+0800\n" | |
13 | "Last-Translator: xpol <xpolife@gmail.com>\n" |
|
13 | "Last-Translator: xpol <xpolife@gmail.com>\n" | |
14 | "Language-Team: mikespook\n" |
|
14 | "Language-Team: mikespook\n" | |
15 | "Plural-Forms: nplurals=1; plural=0\n" |
|
15 | "Plural-Forms: nplurals=1; plural=0;\n" | |
16 | "MIME-Version: 1.0\n" |
|
16 | "MIME-Version: 1.0\n" | |
17 | "Content-Type: text/plain; charset=utf-8\n" |
|
17 | "Content-Type: text/plain; charset=utf-8\n" | |
18 | "Content-Transfer-Encoding: 8bit\n" |
|
18 | "Content-Transfer-Encoding: 8bit\n" | |
19 | "Generated-By: Babel 0.9.6\n" |
|
19 | "Generated-By: Babel 0.9.6\n" | |
|
20 | "X-Generator: Poedit 1.5.4\n" | |||
20 |
|
21 | |||
21 | #: rhodecode/controllers/changelog.py:95 |
|
22 | #: rhodecode/controllers/changelog.py:95 | |
22 | msgid "All Branches" |
|
23 | msgid "All Branches" | |
@@ -43,8 +44,8 b' msgstr "\xe7\x8a\xb6\xe6\x80\x81\xe4\xbf\xae\xe6\x94\xb9\xe4\xb8\xba%s"' | |||||
43 |
|
44 | |||
44 | #: rhodecode/controllers/changeset.py:345 |
|
45 | #: rhodecode/controllers/changeset.py:345 | |
45 | msgid "" |
|
46 | msgid "" | |
46 | "Changing status on a changeset associated witha closed pull request is " |
|
47 | "Changing status on a changeset associated witha closed pull request is not " | |
47 |
" |
|
48 | "allowed" | |
48 | msgstr "不允许修改已关闭拉取请求的修订集状态" |
|
49 | msgstr "不允许修改已关闭拉取请求的修订集状态" | |
49 |
|
50 | |||
50 | #: rhodecode/controllers/compare.py:75 |
|
51 | #: rhodecode/controllers/compare.py:75 | |
@@ -58,7 +59,8 b' msgid "Home page"' | |||||
58 | msgstr "主页" |
|
59 | msgstr "主页" | |
59 |
|
60 | |||
60 | #: rhodecode/controllers/error.py:98 |
|
61 | #: rhodecode/controllers/error.py:98 | |
61 | msgid "The request could not be understood by the server due to malformed syntax." |
|
62 | msgid "" | |
|
63 | "The request could not be understood by the server due to malformed syntax." | |||
62 | msgstr "由于错误的语法,服务器无法对请求进行响应。" |
|
64 | msgstr "由于错误的语法,服务器无法对请求进行响应。" | |
63 |
|
65 | |||
64 | #: rhodecode/controllers/error.py:101 |
|
66 | #: rhodecode/controllers/error.py:101 | |
@@ -211,8 +213,7 b' msgstr "\xe5\xaf\x86\xe7\xa0\x81\xe9\x87\x8d\xe7\xbd\xae\xe9\x93\xbe\xe6\x8e\xa5\xe5\xb7\xb2\xe7\xbb\x8f\xe5\x8f\x91\xe9\x80\x81"' | |||||
211 |
|
213 | |||
212 | #: rhodecode/controllers/login.py:184 |
|
214 | #: rhodecode/controllers/login.py:184 | |
213 | msgid "" |
|
215 | msgid "" | |
214 | "Your password reset was successful, new password has been sent to your " |
|
216 | "Your password reset was successful, new password has been sent to your email" | |
215 | "email" |
|
|||
216 | msgstr "密码已经成功重置,新密码已经发送到你的邮箱" |
|
217 | msgstr "密码已经成功重置,新密码已经发送到你的邮箱" | |
217 |
|
218 | |||
218 | #: rhodecode/controllers/pullrequests.py:76 rhodecode/model/scm.py:556 |
|
219 | #: rhodecode/controllers/pullrequests.py:76 rhodecode/model/scm.py:556 | |
@@ -240,8 +241,9 b' msgid "Successfully deleted pull request' | |||||
240 | msgstr "成功删除拉取请求" |
|
241 | msgstr "成功删除拉取请求" | |
241 |
|
242 | |||
242 | #: rhodecode/controllers/pullrequests.py:452 |
|
243 | #: rhodecode/controllers/pullrequests.py:452 | |
243 | msgid "Closing pull request on other statuses than rejected or approved forbidden" |
|
244 | msgid "" | |
244 | msgstr "" |
|
245 | "Closing pull request on other statuses than rejected or approved forbidden" | |
|
246 | msgstr "只能以批准或者驳回的状态关闭拉取请求" | |||
245 |
|
247 | |||
246 | #: rhodecode/controllers/search.py:134 |
|
248 | #: rhodecode/controllers/search.py:134 | |
247 | msgid "Invalid search query. Try quoting it." |
|
249 | msgid "Invalid search query. Try quoting it." | |
@@ -308,14 +310,12 b' msgid "Statistics are disabled for this ' | |||||
308 | msgstr "该版本库统计功能已经禁用" |
|
310 | msgstr "该版本库统计功能已经禁用" | |
309 |
|
311 | |||
310 | #: rhodecode/controllers/admin/defaults.py:96 |
|
312 | #: rhodecode/controllers/admin/defaults.py:96 | |
311 | #, fuzzy |
|
|||
312 | msgid "Default settings updated successfully" |
|
313 | msgid "Default settings updated successfully" | |
313 |
msgstr " |
|
314 | msgstr "默认设置已经成功更新" | |
314 |
|
315 | |||
315 | #: rhodecode/controllers/admin/defaults.py:110 |
|
316 | #: rhodecode/controllers/admin/defaults.py:110 | |
316 | #, fuzzy |
|
|||
317 | msgid "error occurred during update of defaults" |
|
317 | msgid "error occurred during update of defaults" | |
318 |
msgstr "更新 |
|
318 | msgstr "更新默认设置时发生错误" | |
319 |
|
319 | |||
320 | #: rhodecode/controllers/admin/ldap_settings.py:50 |
|
320 | #: rhodecode/controllers/admin/ldap_settings.py:50 | |
321 | msgid "BASE" |
|
321 | msgid "BASE" | |
@@ -654,11 +654,11 b' msgstr "\xe6\x97\xa0\xe6\xb3\x95\xe7\xbc\x96\xe8\xbe\x91\xe8\xaf\xa5\xe7\x94\xa8\xe6\x88\xb7"' | |||||
654 |
|
654 | |||
655 | #: rhodecode/controllers/admin/users.py:272 |
|
655 | #: rhodecode/controllers/admin/users.py:272 | |
656 | msgid "Granted 'repository create' permission to user" |
|
656 | msgid "Granted 'repository create' permission to user" | |
657 |
msgstr "已授予用户 |
|
657 | msgstr "已授予用户“创建版本库”的权限" | |
658 |
|
658 | |||
659 | #: rhodecode/controllers/admin/users.py:277 |
|
659 | #: rhodecode/controllers/admin/users.py:277 | |
660 | msgid "Revoked 'repository create' permission to user" |
|
660 | msgid "Revoked 'repository create' permission to user" | |
661 |
msgstr "已撤销用户 |
|
661 | msgstr "已撤销用户“创建版本库”的权限" | |
662 |
|
662 | |||
663 | #: rhodecode/controllers/admin/users.py:283 |
|
663 | #: rhodecode/controllers/admin/users.py:283 | |
664 | msgid "Granted 'repository fork' permission to user" |
|
664 | msgid "Granted 'repository fork' permission to user" | |
@@ -716,19 +716,19 b' msgstr "\xe5\x88\xa0\xe9\x99\xa4\xe7\x94\xa8\xe6\x88\xb7\xe7\xbb\x84\xe6\x97\xb6\xe5\x8f\x91\xe7\x94\x9f\xe9\x94\x99\xe8\xaf\xaf"' | |||||
716 |
|
716 | |||
717 | #: rhodecode/controllers/admin/users_groups.py:257 |
|
717 | #: rhodecode/controllers/admin/users_groups.py:257 | |
718 | msgid "Granted 'repository create' permission to users group" |
|
718 | msgid "Granted 'repository create' permission to users group" | |
719 |
msgstr "已授予用户组 |
|
719 | msgstr "已授予用户组“创建版本库”的权限" | |
720 |
|
720 | |||
721 | #: rhodecode/controllers/admin/users_groups.py:262 |
|
721 | #: rhodecode/controllers/admin/users_groups.py:262 | |
722 | msgid "Revoked 'repository create' permission to users group" |
|
722 | msgid "Revoked 'repository create' permission to users group" | |
723 |
msgstr "已撤销用户组 |
|
723 | msgstr "已撤销用户组“创建版本库”的权限" | |
724 |
|
724 | |||
725 | #: rhodecode/controllers/admin/users_groups.py:268 |
|
725 | #: rhodecode/controllers/admin/users_groups.py:268 | |
726 | msgid "Granted 'repository fork' permission to users group" |
|
726 | msgid "Granted 'repository fork' permission to users group" | |
727 |
msgstr "已授予用户组 |
|
727 | msgstr "已授予用户组“复刻版本库”的权限" | |
728 |
|
728 | |||
729 | #: rhodecode/controllers/admin/users_groups.py:273 |
|
729 | #: rhodecode/controllers/admin/users_groups.py:273 | |
730 | msgid "Revoked 'repository fork' permission to users group" |
|
730 | msgid "Revoked 'repository fork' permission to users group" | |
731 |
msgstr "已撤销用户组 |
|
731 | msgstr "已撤销用户组“复刻版本库”的权限" | |
732 |
|
732 | |||
733 | #: rhodecode/lib/auth.py:499 |
|
733 | #: rhodecode/lib/auth.py:499 | |
734 | msgid "You need to be a registered user to perform this action" |
|
734 | msgid "You need to be a registered user to perform this action" | |
@@ -743,7 +743,8 b' msgid "binary file"' | |||||
743 | msgstr "二进制文件" |
|
743 | msgstr "二进制文件" | |
744 |
|
744 | |||
745 | #: rhodecode/lib/diffs.py:90 |
|
745 | #: rhodecode/lib/diffs.py:90 | |
746 | msgid "Changeset was too big and was cut off, use diff menu to display this diff" |
|
746 | msgid "" | |
|
747 | "Changeset was too big and was cut off, use diff menu to display this diff" | |||
747 | msgstr "修订集因过大而被截断,可查看原始修订集作为替代" |
|
748 | msgstr "修订集因过大而被截断,可查看原始修订集作为替代" | |
748 |
|
749 | |||
749 | #: rhodecode/lib/diffs.py:100 |
|
750 | #: rhodecode/lib/diffs.py:100 | |
@@ -795,7 +796,8 b' msgstr "\xe8\xbf\x98\xe6\x9c\x89"' | |||||
795 | msgid "%s more" |
|
796 | msgid "%s more" | |
796 | msgstr "%s个" |
|
797 | msgstr "%s个" | |
797 |
|
798 | |||
798 | #: rhodecode/lib/helpers.py:617 rhodecode/templates/changelog/changelog.html:51 |
|
799 | #: rhodecode/lib/helpers.py:617 | |
|
800 | #: rhodecode/templates/changelog/changelog.html:51 | |||
799 | msgid "revisions" |
|
801 | msgid "revisions" | |
800 | msgstr "修订" |
|
802 | msgstr "修订" | |
801 |
|
803 | |||
@@ -899,10 +901,11 b' msgstr "\xe6\xb2\xa1\xe6\x9c\x89\xe6\x96\x87\xe4\xbb\xb6"' | |||||
899 | #: rhodecode/lib/helpers.py:1163 |
|
901 | #: rhodecode/lib/helpers.py:1163 | |
900 | #, python-format |
|
902 | #, python-format | |
901 | msgid "" |
|
903 | msgid "" | |
902 | "%s repository is not mapped to db perhaps it was created or renamed from " |
|
904 | "%s repository is not mapped to db perhaps it was created or renamed from the " | |
903 |
" |
|
905 | "filesystem please run the application again in order to rescan repositories" | |
904 | "repositories" |
|
906 | msgstr "" | |
905 |
|
|
907 | "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重" | |
|
908 | "新扫描版本库" | |||
906 |
|
909 | |||
907 | #: rhodecode/lib/utils2.py:403 |
|
910 | #: rhodecode/lib/utils2.py:403 | |
908 | #, python-format |
|
911 | #, python-format | |
@@ -1130,9 +1133,10 b' msgstr "\xe7\x94\xb1\xe4\xba\x8e\xe6\x98\xaf\xe7\xb3\xbb\xe7\xbb\x9f\xe5\xb8\x90\xe5\x8f\xb7\xef\xbc\x8c\xe6\x97\xa0\xe6\xb3\x95\xe5\x88\xa0\xe9\x99\xa4\xe8\xaf\xa5\xe7\x94\xa8\xe6\x88\xb7"' | |||||
1130 | #: rhodecode/model/user.py:333 |
|
1133 | #: rhodecode/model/user.py:333 | |
1131 | #, python-format |
|
1134 | #, python-format | |
1132 | msgid "" |
|
1135 | msgid "" | |
1133 | "user \"%s\" still owns %s repositories and cannot be removed. Switch " |
|
1136 | "user \"%s\" still owns %s repositories and cannot be removed. Switch owners " | |
1134 |
"o |
|
1137 | "or remove those repositories. %s" | |
1135 | msgstr "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s" |
|
1138 | msgstr "" | |
|
1139 | "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s" | |||
1136 |
|
1140 | |||
1137 | #: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37 |
|
1141 | #: rhodecode/model/validators.py:36 rhodecode/model/validators.py:37 | |
1138 | msgid "Value cannot be an empty list" |
|
1142 | msgid "Value cannot be an empty list" | |
@@ -1150,9 +1154,10 b' msgstr "\xe4\xb8\x8d\xe5\x85\x81\xe8\xae\xb8\xe7\x94\xa8\xe6\x88\xb7\xe5\x90\x8d \\"%(username)s\\""' | |||||
1150 |
|
1154 | |||
1151 | #: rhodecode/model/validators.py:87 |
|
1155 | #: rhodecode/model/validators.py:87 | |
1152 | msgid "" |
|
1156 | msgid "" | |
1153 | "Username may only contain alphanumeric characters underscores, periods or" |
|
1157 | "Username may only contain alphanumeric characters underscores, periods or " | |
1154 |
" |
|
1158 | "dashes and must begin with alphanumeric character" | |
1155 | msgstr "只能使用字母、数字、下划线、小数点或减号作为用户名,且必须由数字或字母开头" |
|
1159 | msgstr "" | |
|
1160 | "只能使用字母、数字、下划线、小数点或减号作为用户名,且必须由数字或字母开头" | |||
1156 |
|
1161 | |||
1157 | #: rhodecode/model/validators.py:115 |
|
1162 | #: rhodecode/model/validators.py:115 | |
1158 | #, python-format |
|
1163 | #, python-format | |
@@ -1172,7 +1177,8 b' msgstr "\xe7\x94\xa8\xe6\x88\xb7\xe7\xbb\x84 \\"%(usersgroup)s\\" \xe5\xb7\xb2\xe7\xbb\x8f\xe5\xad\x98\xe5\x9c\xa8"' | |||||
1172 | msgid "" |
|
1177 | msgid "" | |
1173 | "users group name may only contain alphanumeric characters underscores, " |
|
1178 | "users group name may only contain alphanumeric characters underscores, " | |
1174 | "periods or dashes and must begin with alphanumeric character" |
|
1179 | "periods or dashes and must begin with alphanumeric character" | |
1175 | msgstr "只能使用字母、数字、下划线、小数点或减号作为用户组名,且必须由数字或字母开头" |
|
1180 | msgstr "" | |
|
1181 | "只能使用字母、数字、下划线、小数点或减号作为用户组名,且必须由数字或字母开头" | |||
1176 |
|
1182 | |||
1177 | #: rhodecode/model/validators.py:175 |
|
1183 | #: rhodecode/model/validators.py:175 | |
1178 | msgid "Cannot assign this group as parent" |
|
1184 | msgid "Cannot assign this group as parent" | |
@@ -1263,8 +1269,8 b' msgstr "\xe9\x82\xae\xe4\xbb\xb6\xe5\x9c\xb0\xe5\x9d\x80\\"%(email)s\\"\xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8"' | |||||
1263 |
|
1269 | |||
1264 | #: rhodecode/model/validators.py:663 |
|
1270 | #: rhodecode/model/validators.py:663 | |
1265 | msgid "" |
|
1271 | msgid "" | |
1266 | "The LDAP Login attribute of the CN must be specified - this is the name " |
|
1272 | "The LDAP Login attribute of the CN must be specified - this is the name of " | |
1267 |
" |
|
1273 | "the attribute that is equivalent to \"username\"" | |
1268 | msgstr "LDAP 登陆属性的 CN 必须指定 - 这个名字作为用户名" |
|
1274 | msgstr "LDAP 登陆属性的 CN 必须指定 - 这个名字作为用户名" | |
1269 |
|
1275 | |||
1270 | #: rhodecode/model/validators.py:682 |
|
1276 | #: rhodecode/model/validators.py:682 | |
@@ -1606,22 +1612,20 b' msgid "Admin journal"' | |||||
1606 | msgstr "系统日志" |
|
1612 | msgstr "系统日志" | |
1607 |
|
1613 | |||
1608 | #: rhodecode/templates/admin/admin.html:10 |
|
1614 | #: rhodecode/templates/admin/admin.html:10 | |
1609 | #, fuzzy |
|
|||
1610 | msgid "journal filter..." |
|
1615 | msgid "journal filter..." | |
1611 |
msgstr " |
|
1616 | msgstr "日志过滤..." | |
1612 |
|
1617 | |||
1613 | #: rhodecode/templates/admin/admin.html:12 |
|
1618 | #: rhodecode/templates/admin/admin.html:12 | |
1614 | #: rhodecode/templates/journal/journal.html:11 |
|
1619 | #: rhodecode/templates/journal/journal.html:11 | |
1615 | #, fuzzy |
|
|||
1616 | msgid "filter" |
|
1620 | msgid "filter" | |
1617 |
msgstr " |
|
1621 | msgstr "过滤" | |
1618 |
|
1622 | |||
1619 | #: rhodecode/templates/admin/admin.html:13 |
|
1623 | #: rhodecode/templates/admin/admin.html:13 | |
1620 | #: rhodecode/templates/journal/journal.html:12 |
|
1624 | #: rhodecode/templates/journal/journal.html:12 | |
1621 | #, python-format |
|
1625 | #, python-format | |
1622 | msgid "%s entry" |
|
1626 | msgid "%s entry" | |
1623 | msgid_plural "%s entries" |
|
1627 | msgid_plural "%s entries" | |
1624 | msgstr[0] "" |
|
1628 | msgstr[0] "%s条" | |
1625 |
|
1629 | |||
1626 | #: rhodecode/templates/admin/admin_log.html:6 |
|
1630 | #: rhodecode/templates/admin/admin_log.html:6 | |
1627 | #: rhodecode/templates/admin/repos/repos.html:74 |
|
1631 | #: rhodecode/templates/admin/repos/repos.html:74 | |
@@ -1657,14 +1661,12 b' msgstr "\xe6\x97\xa0\xe6\x93\x8d\xe4\xbd\x9c"' | |||||
1657 |
|
1661 | |||
1658 | #: rhodecode/templates/admin/defaults/defaults.html:5 |
|
1662 | #: rhodecode/templates/admin/defaults/defaults.html:5 | |
1659 | #: rhodecode/templates/admin/defaults/defaults.html:25 |
|
1663 | #: rhodecode/templates/admin/defaults/defaults.html:25 | |
1660 | #, fuzzy |
|
|||
1661 | msgid "Repositories defaults" |
|
1664 | msgid "Repositories defaults" | |
1662 |
msgstr "版本库 |
|
1665 | msgstr "版本库默认设置" | |
1663 |
|
1666 | |||
1664 | #: rhodecode/templates/admin/defaults/defaults.html:11 |
|
1667 | #: rhodecode/templates/admin/defaults/defaults.html:11 | |
1665 | #, fuzzy |
|
|||
1666 | msgid "Defaults" |
|
1668 | msgid "Defaults" | |
1667 | msgstr "默认" |
|
1669 | msgstr "默认设置" | |
1668 |
|
1670 | |||
1669 | #: rhodecode/templates/admin/defaults/defaults.html:35 |
|
1671 | #: rhodecode/templates/admin/defaults/defaults.html:35 | |
1670 | #: rhodecode/templates/admin/repos/repo_add_base.html:38 |
|
1672 | #: rhodecode/templates/admin/repos/repo_add_base.html:38 | |
@@ -1858,9 +1860,10 b' msgstr "\xe5\x8c\xbf\xe5\x90\x8d\xe8\xae\xbf\xe9\x97\xae"' | |||||
1858 | #: rhodecode/templates/admin/permissions/permissions.html:49 |
|
1860 | #: rhodecode/templates/admin/permissions/permissions.html:49 | |
1859 | msgid "" |
|
1861 | msgid "" | |
1860 | "All default permissions on each repository will be reset to choosen " |
|
1862 | "All default permissions on each repository will be reset to choosen " | |
1861 | "permission, note that all custom default permission on repositories will " |
|
1863 | "permission, note that all custom default permission on repositories will be " | |
1862 |
" |
|
1864 | "lost" | |
1863 | msgstr "所有版本库的默认权限将被重置到选择的权限,所有版本库的自定义权限将被丢弃" |
|
1865 | msgstr "" | |
|
1866 | "所有版本库的默认权限将被重置到选择的权限,所有版本库的自定义权限将被丢弃" | |||
1864 |
|
1867 | |||
1865 | #: rhodecode/templates/admin/permissions/permissions.html:50 |
|
1868 | #: rhodecode/templates/admin/permissions/permissions.html:50 | |
1866 | #: rhodecode/templates/admin/permissions/permissions.html:63 |
|
1869 | #: rhodecode/templates/admin/permissions/permissions.html:63 | |
@@ -1877,12 +1880,12 b' msgid "Repository group"' | |||||
1877 | msgstr "版本库组" |
|
1880 | msgstr "版本库组" | |
1878 |
|
1881 | |||
1879 | #: rhodecode/templates/admin/permissions/permissions.html:62 |
|
1882 | #: rhodecode/templates/admin/permissions/permissions.html:62 | |
1880 | #, fuzzy |
|
|||
1881 | msgid "" |
|
1883 | msgid "" | |
1882 | "All default permissions on each repository group will be reset to choosen" |
|
1884 | "All default permissions on each repository group will be reset to choosen " | |
1883 |
" |
|
1885 | "permission, note that all custom default permission on repositories group " | |
1884 |
" |
|
1886 | "will be lost" | |
1885 | msgstr "所有版本库的默认权限将被重置到选择的权限,所有版本库的自定义权限将被丢弃" |
|
1887 | msgstr "" | |
|
1888 | "所有版本库组的默认权限将被重置到选择的权限,所有版本库组的自定义权限将被丢弃" | |||
1886 |
|
1889 | |||
1887 | #: rhodecode/templates/admin/permissions/permissions.html:69 |
|
1890 | #: rhodecode/templates/admin/permissions/permissions.html:69 | |
1888 | msgid "Registration" |
|
1891 | msgid "Registration" | |
@@ -1955,7 +1958,8 b' msgstr "\xe6\x96\x87\xe4\xbb\xb6\xe6\xb5\x8f\xe8\xa7\x88\xe3\x80\x81\xe4\xb8\x8b\xe8\xbd\xbd\xe3\x80\x81whoosh\xe5\x92\x8cREADME\xe7\x9a\x84\xe9\xbb\x98\xe8\xae\xa4\xe4\xbf\xae\xe8\xae\xa2\xe7\x89\x88\xe6\x9c\xac"' | |||||
1955 | #: rhodecode/templates/admin/repos/repo_edit.html:79 |
|
1958 | #: rhodecode/templates/admin/repos/repo_edit.html:79 | |
1956 | #: rhodecode/templates/forks/fork.html:63 |
|
1959 | #: rhodecode/templates/forks/fork.html:63 | |
1957 | #: rhodecode/templates/settings/repo_settings.html:70 |
|
1960 | #: rhodecode/templates/settings/repo_settings.html:70 | |
1958 | msgid "Keep it short and to the point. Use a README file for longer descriptions." |
|
1961 | msgid "" | |
|
1962 | "Keep it short and to the point. Use a README file for longer descriptions." | |||
1959 | msgstr "保持简短。用README文件来写更长的描述。" |
|
1963 | msgstr "保持简短。用README文件来写更长的描述。" | |
1960 |
|
1964 | |||
1961 | #: rhodecode/templates/admin/repos/repo_add_base.html:73 |
|
1965 | #: rhodecode/templates/admin/repos/repo_add_base.html:73 | |
@@ -2064,8 +2068,8 b' msgstr "\xe7\xa1\xae\xe8\xae\xa4\xe6\xb8\x85\xe9\x99\xa4\xe7\x89\x88\xe6\x9c\xac\xe5\xba\x93\xe7\xbc\x93\xe5\xad\x98"' | |||||
2064 |
|
2068 | |||
2065 | #: rhodecode/templates/admin/repos/repo_edit.html:193 |
|
2069 | #: rhodecode/templates/admin/repos/repo_edit.html:193 | |
2066 | msgid "" |
|
2070 | msgid "" | |
2067 | "Manually invalidate cache for this repository. On first access repository" |
|
2071 | "Manually invalidate cache for this repository. On first access repository " | |
2068 |
" |
|
2072 | "will be cached again" | |
2069 | msgstr "手动清除版本库缓存。之后第一次访问的时候将重建缓存" |
|
2073 | msgstr "手动清除版本库缓存。之后第一次访问的时候将重建缓存" | |
2070 |
|
2074 | |||
2071 | #: rhodecode/templates/admin/repos/repo_edit.html:198 |
|
2075 | #: rhodecode/templates/admin/repos/repo_edit.html:198 | |
@@ -2105,8 +2109,8 b' msgstr "\xe6\xb7\xbb\xe5\x8a\xa0\xe5\x88\xb0\xe5\x85\xac\xe5\x85\xb1\xe6\x97\xa5\xe5\xbf\x97"' | |||||
2105 |
|
2109 | |||
2106 | #: rhodecode/templates/admin/repos/repo_edit.html:231 |
|
2110 | #: rhodecode/templates/admin/repos/repo_edit.html:231 | |
2107 | msgid "" |
|
2111 | msgid "" | |
2108 | "All actions made on this repository will be accessible to everyone in " |
|
2112 | "All actions made on this repository will be accessible to everyone in public " | |
2109 |
" |
|
2113 | "journal" | |
2110 | msgstr "任何人都可以在公共日志上看到这个版本库上的所有动作" |
|
2114 | msgstr "任何人都可以在公共日志上看到这个版本库上的所有动作" | |
2111 |
|
2115 | |||
2112 | #: rhodecode/templates/admin/repos/repo_edit.html:238 |
|
2116 | #: rhodecode/templates/admin/repos/repo_edit.html:238 | |
@@ -2134,7 +2138,8 b' msgid "Repository is not locked"' | |||||
2134 | msgstr "版本库未锁定" |
|
2138 | msgstr "版本库未锁定" | |
2135 |
|
2139 | |||
2136 | #: rhodecode/templates/admin/repos/repo_edit.html:252 |
|
2140 | #: rhodecode/templates/admin/repos/repo_edit.html:252 | |
2137 | msgid "Force locking on repository. Works only when anonymous access is disabled" |
|
2141 | msgid "" | |
|
2142 | "Force locking on repository. Works only when anonymous access is disabled" | |||
2138 | msgstr "强制锁定版本库。只有在禁止匿名访问时候才有效" |
|
2143 | msgstr "强制锁定版本库。只有在禁止匿名访问时候才有效" | |
2139 |
|
2144 | |||
2140 | #: rhodecode/templates/admin/repos/repo_edit.html:259 |
|
2145 | #: rhodecode/templates/admin/repos/repo_edit.html:259 | |
@@ -2162,14 +2167,13 b' msgstr "\xe7\xa1\xae\xe8\xae\xa4\xe5\x88\xa0\xe9\x99\xa4\xe7\x89\x88\xe6\x9c\xac\xe5\xba\x93"' | |||||
2162 |
|
2167 | |||
2163 | #: rhodecode/templates/admin/repos/repo_edit.html:282 |
|
2168 | #: rhodecode/templates/admin/repos/repo_edit.html:282 | |
2164 | #: rhodecode/templates/settings/repo_settings.html:119 |
|
2169 | #: rhodecode/templates/settings/repo_settings.html:119 | |
2165 | #, fuzzy |
|
|||
2166 | msgid "" |
|
2170 | msgid "" | |
2167 | "This repository will be renamed in a special way in order to be " |
|
2171 | "This repository will be renamed in a special way in order to be unaccesible " | |
2168 |
" |
|
2172 | "for RhodeCode and VCS systems. If you need fully delete it from file system " | |
2169 |
" |
|
2173 | "please do it manually" | |
2170 | msgstr "" |
|
2174 | msgstr "" | |
2171 |
"这个版本库将以特殊的方式重命名这样RhodeCode和版本控制系统将不能访问它。 |
|
2175 | "这个版本库将以特殊的方式重命名这样RhodeCode和版本控制系统将不能访问它。如果需" | |
2172 | " 如果需要从文件系统完全删除,你需要手动操作" |
|
2176 | "要从文件系统完全删除,请要手动操作" | |
2173 |
|
2177 | |||
2174 | #: rhodecode/templates/admin/repos/repo_edit_perms.html:3 |
|
2178 | #: rhodecode/templates/admin/repos/repo_edit_perms.html:3 | |
2175 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3 |
|
2179 | #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3 | |
@@ -2263,7 +2267,8 b' msgstr "\xe8\xae\xbe\xe7\xbd\xae\xe6\x88\x96\xe8\x80\x85\xe6\x92\xa4\xe9\x94\x80\xe8\xaf\xa5\xe7\xbb\x84\xe6\x89\x80\xe6\x9c\x89\xe6\x88\x90\xe5\x91\x98\xe7\x9a\x84\xe6\x9d\x83\xe9\x99\x90\xef\xbc\x8c\xe5\x8c\x85\xe6\x8b\xac\xe7\x89\x88\xe6\x9c\xac\xe5\xba\x93\xe5\x92\x8c\xe5\x85\xb6\xe4\xbb\x96\xe7\xbb\x84"' | |||||
2263 | #: rhodecode/templates/files/files_add.html:15 |
|
2267 | #: rhodecode/templates/files/files_add.html:15 | |
2264 | #: rhodecode/templates/files/files_edit.html:15 |
|
2268 | #: rhodecode/templates/files/files_edit.html:15 | |
2265 | #: rhodecode/templates/followers/followers.html:9 |
|
2269 | #: rhodecode/templates/followers/followers.html:9 | |
2266 |
#: rhodecode/templates/forks/fork.html:9 |
|
2270 | #: rhodecode/templates/forks/fork.html:9 | |
|
2271 | #: rhodecode/templates/forks/forks.html:9 | |||
2267 | #: rhodecode/templates/pullrequests/pullrequest.html:8 |
|
2272 | #: rhodecode/templates/pullrequests/pullrequest.html:8 | |
2268 | #: rhodecode/templates/pullrequests/pullrequest_show.html:8 |
|
2273 | #: rhodecode/templates/pullrequests/pullrequest_show.html:8 | |
2269 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:8 |
|
2274 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:8 | |
@@ -2314,8 +2319,8 b' msgstr "\xe7\xbc\x96\xe8\xbe\x91\xe7\x89\x88\xe6\x9c\xac\xe5\xba\x93\xe7\xbb\x84"' | |||||
2314 |
|
2319 | |||
2315 | #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70 |
|
2320 | #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70 | |
2316 | msgid "" |
|
2321 | msgid "" | |
2317 | "Enable lock-by-pulling on group. This option will be applied to all other" |
|
2322 | "Enable lock-by-pulling on group. This option will be applied to all other " | |
2318 |
" |
|
2323 | "groups and repositories inside" | |
2319 | msgstr "启用组的拉取锁定。这个选项将应用到组内的其他组和版本库" |
|
2324 | msgstr "启用组的拉取锁定。这个选项将应用到组内的其他组和版本库" | |
2320 |
|
2325 | |||
2321 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5 |
|
2326 | #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5 | |
@@ -2391,10 +2396,11 b' msgstr "\xe9\x87\x8d\xe6\x96\xb0\xe6\x89\xab\xe6\x8f\x8f\xe9\x80\x89\xe9\xa1\xb9"' | |||||
2391 |
|
2396 | |||
2392 | #: rhodecode/templates/admin/settings/settings.html:38 |
|
2397 | #: rhodecode/templates/admin/settings/settings.html:38 | |
2393 | msgid "" |
|
2398 | msgid "" | |
2394 | "In case a repository was deleted from filesystem and there are leftovers " |
|
2399 | "In case a repository was deleted from filesystem and there are leftovers in " | |
2395 |
" |
|
2400 | "the database check this option to scan obsolete data in database and remove " | |
2396 | "remove it." |
|
2401 | "it." | |
2397 | msgstr "如果版本库已经从文件系统删除,但数据库仍然有遗留信息,请勾选该项进行清理" |
|
2402 | msgstr "" | |
|
2403 | "如果版本库已经从文件系统删除,但数据库仍然有遗留信息,请勾选该项进行清理" | |||
2398 |
|
2404 | |||
2399 | #: rhodecode/templates/admin/settings/settings.html:39 |
|
2405 | #: rhodecode/templates/admin/settings/settings.html:39 | |
2400 | msgid "destroy old data" |
|
2406 | msgid "destroy old data" | |
@@ -2402,9 +2408,10 b' msgstr "\xe6\xb8\x85\xe7\x90\x86\xe6\x97\xa7\xe6\x95\xb0\xe6\x8d\xae"' | |||||
2402 |
|
2408 | |||
2403 | #: rhodecode/templates/admin/settings/settings.html:41 |
|
2409 | #: rhodecode/templates/admin/settings/settings.html:41 | |
2404 | msgid "" |
|
2410 | msgid "" | |
2405 | "Rescan repositories location for new repositories. Also deletes obsolete " |
|
2411 | "Rescan repositories location for new repositories. Also deletes obsolete if " | |
2406 |
" |
|
2412 | "`destroy` flag is checked " | |
2407 | msgstr "重新扫描版本库路径以发现新版本库。 同时删除过时的,如果设置有 `destroy` 标志" |
|
2413 | msgstr "" | |
|
2414 | "重新扫描版本库路径以发现新版本库。 同时删除过时的,如果设置有 `destroy` 标志" | |||
2408 |
|
2415 | |||
2409 | #: rhodecode/templates/admin/settings/settings.html:46 |
|
2416 | #: rhodecode/templates/admin/settings/settings.html:46 | |
2410 | msgid "Rescan repositories" |
|
2417 | msgid "Rescan repositories" | |
@@ -2494,9 +2501,11 b' msgstr "\xe8\xa6\x81\xe6\xb1\x82\xe4\xbd\xbf\xe7\x94\xa8SSL\xe8\xbf\x9b\xe8\xa1\x8c\xe7\x89\x88\xe6\x9c\xac\xe6\x8e\xa7\xe5\x88\xb6\xe7\xb3\xbb\xe7\xbb\x9f\xe6\x93\x8d\xe4\xbd\x9c"' | |||||
2494 |
|
2501 | |||
2495 | #: rhodecode/templates/admin/settings/settings.html:203 |
|
2502 | #: rhodecode/templates/admin/settings/settings.html:203 | |
2496 | msgid "" |
|
2503 | msgid "" | |
2497 | "RhodeCode will require SSL for pushing or pulling. If SSL is missing it " |
|
2504 | "RhodeCode will require SSL for pushing or pulling. If SSL is missing it will " | |
2498 |
" |
|
2505 | "return HTTP Error 406: Not Acceptable" | |
2499 | msgstr "勾选后RhodeCode将要求使用SSL进行推送和拉取。如果没有使用SSL将返回HTTP 406错误:Not Acceptable" |
|
2506 | msgstr "" | |
|
2507 | "勾选后RhodeCode将要求使用SSL进行推送和拉取。如果没有使用SSL将返回HTTP 406错" | |||
|
2508 | "误:Not Acceptable" | |||
2500 |
|
2509 | |||
2501 | #: rhodecode/templates/admin/settings/settings.html:209 |
|
2510 | #: rhodecode/templates/admin/settings/settings.html:209 | |
2502 | msgid "Hooks" |
|
2511 | msgid "Hooks" | |
@@ -2547,8 +2556,8 b' msgstr "\xe7\x89\x88\xe6\x9c\xac\xe5\xba\x93\xe8\xb7\xaf\xe5\xbe\x84"' | |||||
2547 | #: rhodecode/templates/admin/settings/settings.html:261 |
|
2556 | #: rhodecode/templates/admin/settings/settings.html:261 | |
2548 | msgid "" |
|
2557 | msgid "" | |
2549 | "This a crucial application setting. If you are really sure you need to " |
|
2558 | "This a crucial application setting. If you are really sure you need to " | |
2550 | "change this, you must restart application in order to make this setting " |
|
2559 | "change this, you must restart application in order to make this setting take " | |
2551 |
" |
|
2560 | "effect. Click this label to unlock." | |
2552 | msgstr "这是一个关键设置。如果确认修改该项设置,请重启服务以便设置生效。" |
|
2561 | msgstr "这是一个关键设置。如果确认修改该项设置,请重启服务以便设置生效。" | |
2553 |
|
2562 | |||
2554 | #: rhodecode/templates/admin/settings/settings.html:262 |
|
2563 | #: rhodecode/templates/admin/settings/settings.html:262 | |
@@ -2558,8 +2567,8 b' msgstr "\xe8\xa7\xa3\xe9\x94\x81"' | |||||
2558 |
|
2567 | |||
2559 | #: rhodecode/templates/admin/settings/settings.html:263 |
|
2568 | #: rhodecode/templates/admin/settings/settings.html:263 | |
2560 | msgid "" |
|
2569 | msgid "" | |
2561 | "Location where repositories are stored. After changing this value a " |
|
2570 | "Location where repositories are stored. After changing this value a restart, " | |
2562 |
" |
|
2571 | "and rescan is required" | |
2563 | msgstr "版本库存储路径。 修改后需要重启和重新扫描" |
|
2572 | msgstr "版本库存储路径。 修改后需要重启和重新扫描" | |
2564 |
|
2573 | |||
2565 | #: rhodecode/templates/admin/settings/settings.html:283 |
|
2574 | #: rhodecode/templates/admin/settings/settings.html:283 | |
@@ -2932,7 +2941,8 b' msgid "Products"' | |||||
2932 | msgstr "产品" |
|
2941 | msgstr "产品" | |
2933 |
|
2942 | |||
2934 | #: rhodecode/templates/base/base.html:152 |
|
2943 | #: rhodecode/templates/base/base.html:152 | |
2935 |
#: rhodecode/templates/base/base.html:182 |
|
2944 | #: rhodecode/templates/base/base.html:182 | |
|
2945 | #: rhodecode/templates/base/root.html:47 | |||
2936 | msgid "loading..." |
|
2946 | msgid "loading..." | |
2937 | msgstr "载入中..." |
|
2947 | msgstr "载入中..." | |
2938 |
|
2948 | |||
@@ -2986,7 +2996,8 b' msgstr "\xe7\x89\x88\xe6\x9c\xac\xe5\xba\x93\xe9\x80\x89\xe9\xa1\xb9"' | |||||
2986 | msgid "fork" |
|
2996 | msgid "fork" | |
2987 | msgstr "复刻" |
|
2997 | msgstr "复刻" | |
2988 |
|
2998 | |||
2989 |
#: rhodecode/templates/base/base.html:212 |
|
2999 | #: rhodecode/templates/base/base.html:212 | |
|
3000 | #: rhodecode/templates/base/root.html:50 | |||
2990 | #: rhodecode/templates/changelog/changelog.html:43 |
|
3001 | #: rhodecode/templates/changelog/changelog.html:43 | |
2991 | msgid "Open new pull request" |
|
3002 | msgid "Open new pull request" | |
2992 | msgstr "新建拉取请求" |
|
3003 | msgstr "新建拉取请求" | |
@@ -3017,9 +3028,8 b' msgid "permissions"' | |||||
3017 | msgstr "权限" |
|
3028 | msgstr "权限" | |
3018 |
|
3029 | |||
3019 | #: rhodecode/templates/base/base.html:239 |
|
3030 | #: rhodecode/templates/base/base.html:239 | |
3020 | #, fuzzy |
|
|||
3021 | msgid "defaults" |
|
3031 | msgid "defaults" | |
3022 | msgstr "默认" |
|
3032 | msgstr "默认设置" | |
3023 |
|
3033 | |||
3024 | #: rhodecode/templates/base/base.html:240 |
|
3034 | #: rhodecode/templates/base/base.html:240 | |
3025 | msgid "settings" |
|
3035 | msgid "settings" | |
@@ -3238,9 +3248,8 b' msgid "Changeset"' | |||||
3238 | msgstr "修订集" |
|
3248 | msgstr "修订集" | |
3239 |
|
3249 | |||
3240 | #: rhodecode/templates/changeset/changeset.html:52 |
|
3250 | #: rhodecode/templates/changeset/changeset.html:52 | |
3241 | #, fuzzy |
|
|||
3242 | msgid "No children" |
|
3251 | msgid "No children" | |
3243 |
msgstr " |
|
3252 | msgstr "无子对象" | |
3244 |
|
3253 | |||
3245 | #: rhodecode/templates/changeset/changeset.html:70 |
|
3254 | #: rhodecode/templates/changeset/changeset.html:70 | |
3246 | #: rhodecode/templates/changeset/diff_block.html:20 |
|
3255 | #: rhodecode/templates/changeset/diff_block.html:20 | |
@@ -3302,7 +3311,8 b' msgstr "\xe8\xaf\x84\xe8\xae\xba\xe4\xbd\xbf\xe7\x94\xa8%s\xe8\xaf\xad\xe6\xb3\x95\xe5\xb9\xb6\xe6\x94\xaf\xe6\x8c\x81%s"' | |||||
3302 |
|
3311 | |||
3303 | #: rhodecode/templates/changeset/changeset_file_comment.html:48 |
|
3312 | #: rhodecode/templates/changeset/changeset_file_comment.html:48 | |
3304 | #: rhodecode/templates/changeset/changeset_file_comment.html:123 |
|
3313 | #: rhodecode/templates/changeset/changeset_file_comment.html:123 | |
3305 | msgid "Use @username inside this text to send notification to this RhodeCode user" |
|
3314 | msgid "" | |
|
3315 | "Use @username inside this text to send notification to this RhodeCode user" | |||
3306 | msgstr "在文本中使用 @用户名 以发送通知到该RhodeCode用户" |
|
3316 | msgstr "在文本中使用 @用户名 以发送通知到该RhodeCode用户" | |
3307 |
|
3317 | |||
3308 | #: rhodecode/templates/changeset/changeset_file_comment.html:59 |
|
3318 | #: rhodecode/templates/changeset/changeset_file_comment.html:59 | |
@@ -3433,9 +3443,8 b' msgid "Confirm to delete this user: %s"' | |||||
3433 | msgstr "确认删除用户:%s" |
|
3443 | msgstr "确认删除用户:%s" | |
3434 |
|
3444 | |||
3435 | #: rhodecode/templates/email_templates/changeset_comment.html:10 |
|
3445 | #: rhodecode/templates/email_templates/changeset_comment.html:10 | |
3436 | #, fuzzy |
|
|||
3437 | msgid "New status$" |
|
3446 | msgid "New status$" | |
3438 |
msgstr " |
|
3447 | msgstr "新状态$" | |
3439 |
|
3448 | |||
3440 | #: rhodecode/templates/email_templates/main.html:8 |
|
3449 | #: rhodecode/templates/email_templates/main.html:8 | |
3441 | msgid "This is a notification from RhodeCode." |
|
3450 | msgid "This is a notification from RhodeCode." | |
@@ -3443,29 +3452,28 b' msgstr "\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaaRhodeCode\xe9\x80\x9a\xe7\x9f\xa5\xe3\x80\x82"' | |||||
3443 |
|
3452 | |||
3444 | #: rhodecode/templates/email_templates/password_reset.html:4 |
|
3453 | #: rhodecode/templates/email_templates/password_reset.html:4 | |
3445 | msgid "Hello" |
|
3454 | msgid "Hello" | |
3446 | msgstr "" |
|
3455 | msgstr "你好" | |
3447 |
|
3456 | |||
3448 | #: rhodecode/templates/email_templates/password_reset.html:6 |
|
3457 | #: rhodecode/templates/email_templates/password_reset.html:6 | |
3449 | msgid "We received a request to create a new password for your account." |
|
3458 | msgid "We received a request to create a new password for your account." | |
3450 | msgstr "" |
|
3459 | msgstr "我们收到重置你用户密码的请求。" | |
3451 |
|
3460 | |||
3452 | #: rhodecode/templates/email_templates/password_reset.html:8 |
|
3461 | #: rhodecode/templates/email_templates/password_reset.html:8 | |
3453 | msgid "You can generate it by clicking following URL" |
|
3462 | msgid "You can generate it by clicking following URL" | |
3454 | msgstr "" |
|
3463 | msgstr "点击下面的链接以重新生成密码:" | |
3455 |
|
3464 | |||
3456 | #: rhodecode/templates/email_templates/password_reset.html:12 |
|
3465 | #: rhodecode/templates/email_templates/password_reset.html:12 | |
3457 | msgid "If you didn't request new password please ignore this email." |
|
3466 | msgid "If you didn't request new password please ignore this email." | |
3458 | msgstr "" |
|
3467 | msgstr "如果你没有要求重置密码,请忽略这封邮件。" | |
3459 |
|
3468 | |||
3460 | #: rhodecode/templates/email_templates/pull_request.html:4 |
|
3469 | #: rhodecode/templates/email_templates/pull_request.html:4 | |
3461 | #, python-format |
|
3470 | #, python-format | |
3462 | msgid "" |
|
3471 | msgid "" | |
3463 | "User %s opened pull request for repository %s and wants you to review " |
|
3472 | "User %s opened pull request for repository %s and wants you to review " | |
3464 | "changes." |
|
3473 | "changes." | |
3465 | msgstr "" |
|
3474 | msgstr "用户%s在版本库%s中创建了一个拉取请求需要你检视" | |
3466 |
|
3475 | |||
3467 | #: rhodecode/templates/email_templates/pull_request.html:5 |
|
3476 | #: rhodecode/templates/email_templates/pull_request.html:5 | |
3468 | #, fuzzy |
|
|||
3469 | msgid "title" |
|
3477 | msgid "title" | |
3470 | msgstr "标题" |
|
3478 | msgstr "标题" | |
3471 |
|
3479 | |||
@@ -3476,35 +3484,32 b' msgstr "\xe6\x8f\x8f\xe8\xbf\xb0"' | |||||
3476 |
|
3484 | |||
3477 | #: rhodecode/templates/email_templates/pull_request.html:11 |
|
3485 | #: rhodecode/templates/email_templates/pull_request.html:11 | |
3478 | msgid "revisions for reviewing" |
|
3486 | msgid "revisions for reviewing" | |
3479 | msgstr "" |
|
3487 | msgstr "待检视修订" | |
3480 |
|
3488 | |||
3481 | #: rhodecode/templates/email_templates/pull_request.html:18 |
|
3489 | #: rhodecode/templates/email_templates/pull_request.html:18 | |
3482 | #, fuzzy |
|
|||
3483 | msgid "View this pull request here" |
|
3490 | msgid "View this pull request here" | |
3484 |
msgstr " |
|
3491 | msgstr "查看拉取请求" | |
3485 |
|
3492 | |||
3486 | #: rhodecode/templates/email_templates/pull_request_comment.html:4 |
|
3493 | #: rhodecode/templates/email_templates/pull_request_comment.html:4 | |
3487 |
#, |
|
3494 | #, python-format | |
3488 | msgid "User %s commented on pull request #%s for repository %s" |
|
3495 | msgid "User %s commented on pull request #%s for repository %s" | |
3489 | msgstr "" |
|
3496 | msgstr "用户%s评论了版本库%s的拉取请求%s" | |
3490 |
|
3497 | |||
3491 | #: rhodecode/templates/email_templates/pull_request_comment.html:10 |
|
3498 | #: rhodecode/templates/email_templates/pull_request_comment.html:10 | |
3492 | #, fuzzy |
|
|||
3493 | msgid "New status" |
|
3499 | msgid "New status" | |
3494 |
msgstr " |
|
3500 | msgstr "新状态" | |
3495 |
|
3501 | |||
3496 | #: rhodecode/templates/email_templates/pull_request_comment.html:14 |
|
3502 | #: rhodecode/templates/email_templates/pull_request_comment.html:14 | |
3497 | msgid "View this comment here" |
|
3503 | msgid "View this comment here" | |
3498 | msgstr "" |
|
3504 | msgstr "查看评论" | |
3499 |
|
3505 | |||
3500 | #: rhodecode/templates/email_templates/registration.html:4 |
|
3506 | #: rhodecode/templates/email_templates/registration.html:4 | |
3501 | #, fuzzy |
|
|||
3502 | msgid "A new user have registered in RhodeCode" |
|
3507 | msgid "A new user have registered in RhodeCode" | |
3503 |
msgstr " |
|
3508 | msgstr "新用户注册RhodeCode" | |
3504 |
|
3509 | |||
3505 | #: rhodecode/templates/email_templates/registration.html:9 |
|
3510 | #: rhodecode/templates/email_templates/registration.html:9 | |
3506 | msgid "View this user here" |
|
3511 | msgid "View this user here" | |
3507 | msgstr "" |
|
3512 | msgstr "查看用户" | |
3508 |
|
3513 | |||
3509 | #: rhodecode/templates/errors/error_document.html:46 |
|
3514 | #: rhodecode/templates/errors/error_document.html:46 | |
3510 | #, python-format |
|
3515 | #, python-format | |
@@ -3674,9 +3679,8 b' msgid "show at revision"' | |||||
3674 | msgstr "显示修订" |
|
3679 | msgstr "显示修订" | |
3675 |
|
3680 | |||
3676 | #: rhodecode/templates/files/files_history_box.html:11 |
|
3681 | #: rhodecode/templates/files/files_history_box.html:11 | |
3677 | #, fuzzy |
|
|||
3678 | msgid "show full history" |
|
3682 | msgid "show full history" | |
3679 |
msgstr " |
|
3683 | msgstr "显示全部历史记录" | |
3680 |
|
3684 | |||
3681 | #: rhodecode/templates/files/files_history_box.html:16 |
|
3685 | #: rhodecode/templates/files/files_history_box.html:16 | |
3682 | #, python-format |
|
3686 | #, python-format | |
@@ -3918,9 +3922,8 b' msgid "Compare view"' | |||||
3918 | msgstr "比较显示" |
|
3922 | msgstr "比较显示" | |
3919 |
|
3923 | |||
3920 | #: rhodecode/templates/pullrequests/pullrequest_show.html:112 |
|
3924 | #: rhodecode/templates/pullrequests/pullrequest_show.html:112 | |
3921 | #, fuzzy |
|
|||
3922 | msgid "reviewer" |
|
3925 | msgid "reviewer" | |
3923 |
msgstr " |
|
3926 | msgstr "检视者" | |
3924 |
|
3927 | |||
3925 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4 |
|
3928 | #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4 | |
3926 | msgid "all pull requests" |
|
3929 | msgid "all pull requests" | |
@@ -3987,14 +3990,12 b' msgid "%s Settings"' | |||||
3987 | msgstr "%s设置" |
|
3990 | msgstr "%s设置" | |
3988 |
|
3991 | |||
3989 | #: rhodecode/templates/settings/repo_settings.html:102 |
|
3992 | #: rhodecode/templates/settings/repo_settings.html:102 | |
3990 | #, fuzzy |
|
|||
3991 | msgid "Delete repository" |
|
3993 | msgid "Delete repository" | |
3992 |
msgstr " |
|
3994 | msgstr "删除版本库" | |
3993 |
|
3995 | |||
3994 | #: rhodecode/templates/settings/repo_settings.html:109 |
|
3996 | #: rhodecode/templates/settings/repo_settings.html:109 | |
3995 | #, fuzzy |
|
|||
3996 | msgid "Remove repo" |
|
3997 | msgid "Remove repo" | |
3997 | msgstr "删除" |
|
3998 | msgstr "删除版本库" | |
3998 |
|
3999 | |||
3999 | #: rhodecode/templates/shortlog/shortlog.html:5 |
|
4000 | #: rhodecode/templates/shortlog/shortlog.html:5 | |
4000 | #, python-format |
|
4001 | #, python-format | |
@@ -4196,20 +4197,3 b' msgstr "%s\xe6\xa0\x87\xe7\xad\xbe"' | |||||
4196 | #: rhodecode/templates/tags/tags.html:29 |
|
4197 | #: rhodecode/templates/tags/tags.html:29 | |
4197 | msgid "Compare tags" |
|
4198 | msgid "Compare tags" | |
4198 | msgstr "比较标签" |
|
4199 | msgstr "比较标签" | |
4199 |
|
||||
4200 | #~ msgid "" |
|
|||
4201 | #~ "%s repository is not mapped to db" |
|
|||
4202 | #~ " perhaps it was created or renamed" |
|
|||
4203 | #~ " from the file system please run " |
|
|||
4204 | #~ "the application again in order to " |
|
|||
4205 | #~ "rescan repositories" |
|
|||
4206 | #~ msgstr " 版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重新扫描版本库" |
|
|||
4207 |
|
||||
4208 | #~ msgid "" |
|
|||
4209 | #~ "%s repository is not mapped to db" |
|
|||
4210 | #~ " perhaps it was moved or renamed " |
|
|||
4211 | #~ "from the filesystem please run the " |
|
|||
4212 | #~ "application again in order to rescan " |
|
|||
4213 | #~ "repositories" |
|
|||
4214 | #~ msgstr "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重新扫描版本库" |
|
|||
4215 |
|
@@ -45,7 +45,8 b' from rhodecode.lib.auth_ldap import Auth' | |||||
45 |
|
45 | |||
46 | from rhodecode.model import meta |
|
46 | from rhodecode.model import meta | |
47 | from rhodecode.model.user import UserModel |
|
47 | from rhodecode.model.user import UserModel | |
48 | from rhodecode.model.db import Permission, RhodeCodeSetting, User |
|
48 | from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap | |
|
49 | from rhodecode.lib.caching_query import FromCache | |||
49 |
|
50 | |||
50 | log = logging.getLogger(__name__) |
|
51 | log = logging.getLogger(__name__) | |
51 |
|
52 | |||
@@ -269,21 +270,34 b' def login_container_auth(username):' | |||||
269 | return user |
|
270 | return user | |
270 |
|
271 | |||
271 |
|
272 | |||
272 | def get_container_username(environ, config): |
|
273 | def get_container_username(environ, config, clean_username=False): | |
|
274 | """ | |||
|
275 | Get's the container_auth username (or email). It tries to get username | |||
|
276 | from REMOTE_USER if container_auth_enabled is enabled, if that fails | |||
|
277 | it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled | |||
|
278 | is enabled. clean_username extracts the username from this data if it's | |||
|
279 | having @ in it. | |||
|
280 | ||||
|
281 | :param environ: | |||
|
282 | :param config: | |||
|
283 | :param clean_username: | |||
|
284 | """ | |||
273 | username = None |
|
285 | username = None | |
274 |
|
286 | |||
275 | if str2bool(config.get('container_auth_enabled', False)): |
|
287 | if str2bool(config.get('container_auth_enabled', False)): | |
276 | from paste.httpheaders import REMOTE_USER |
|
288 | from paste.httpheaders import REMOTE_USER | |
277 | username = REMOTE_USER(environ) |
|
289 | username = REMOTE_USER(environ) | |
|
290 | log.debug('extracted REMOTE_USER:%s' % (username)) | |||
278 |
|
291 | |||
279 | if not username and str2bool(config.get('proxypass_auth_enabled', False)): |
|
292 | if not username and str2bool(config.get('proxypass_auth_enabled', False)): | |
280 | username = environ.get('HTTP_X_FORWARDED_USER') |
|
293 | username = environ.get('HTTP_X_FORWARDED_USER') | |
|
294 | log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username)) | |||
281 |
|
295 | |||
282 | if username: |
|
296 | if username and clean_username: | |
283 | # Removing realm and domain from username |
|
297 | # Removing realm and domain from username | |
284 | username = username.partition('@')[0] |
|
298 | username = username.partition('@')[0] | |
285 | username = username.rpartition('\\')[2] |
|
299 | username = username.rpartition('\\')[2] | |
286 |
|
|
300 | log.debug('Received username %s from container' % username) | |
287 |
|
301 | |||
288 | return username |
|
302 | return username | |
289 |
|
303 | |||
@@ -313,11 +327,12 b' class AuthUser(object):' | |||||
313 | in |
|
327 | in | |
314 | """ |
|
328 | """ | |
315 |
|
329 | |||
316 | def __init__(self, user_id=None, api_key=None, username=None): |
|
330 | def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None): | |
317 |
|
331 | |||
318 | self.user_id = user_id |
|
332 | self.user_id = user_id | |
319 | self.api_key = None |
|
333 | self.api_key = None | |
320 | self.username = username |
|
334 | self.username = username | |
|
335 | self.ip_addr = ip_addr | |||
321 |
|
336 | |||
322 | self.name = '' |
|
337 | self.name = '' | |
323 | self.lastname = '' |
|
338 | self.lastname = '' | |
@@ -380,6 +395,24 b' class AuthUser(object):' | |||||
380 | def is_admin(self): |
|
395 | def is_admin(self): | |
381 | return self.admin |
|
396 | return self.admin | |
382 |
|
397 | |||
|
398 | @property | |||
|
399 | def ip_allowed(self): | |||
|
400 | """ | |||
|
401 | Checks if ip_addr used in constructor is allowed from defined list of | |||
|
402 | allowed ip_addresses for user | |||
|
403 | ||||
|
404 | :returns: boolean, True if ip is in allowed ip range | |||
|
405 | """ | |||
|
406 | #check IP | |||
|
407 | allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True) | |||
|
408 | if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips): | |||
|
409 | log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips)) | |||
|
410 | return True | |||
|
411 | else: | |||
|
412 | log.info('Access for IP:%s forbidden, ' | |||
|
413 | 'not in %s' % (self.ip_addr, allowed_ips)) | |||
|
414 | return False | |||
|
415 | ||||
383 | def __repr__(self): |
|
416 | def __repr__(self): | |
384 | return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username, |
|
417 | return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username, | |
385 | self.is_authenticated) |
|
418 | self.is_authenticated) | |
@@ -406,6 +439,17 b' class AuthUser(object):' | |||||
406 | api_key = cookie_store.get('api_key') |
|
439 | api_key = cookie_store.get('api_key') | |
407 | return AuthUser(user_id, api_key, username) |
|
440 | return AuthUser(user_id, api_key, username) | |
408 |
|
441 | |||
|
442 | @classmethod | |||
|
443 | def get_allowed_ips(cls, user_id, cache=False): | |||
|
444 | _set = set() | |||
|
445 | user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id) | |||
|
446 | if cache: | |||
|
447 | user_ips = user_ips.options(FromCache("sql_cache_short", | |||
|
448 | "get_user_ips_%s" % user_id)) | |||
|
449 | for ip in user_ips: | |||
|
450 | _set.add(ip.ip_addr) | |||
|
451 | return _set or set(['0.0.0.0/0']) | |||
|
452 | ||||
409 |
|
453 | |||
410 | def set_available_permissions(config): |
|
454 | def set_available_permissions(config): | |
411 | """ |
|
455 | """ | |
@@ -450,6 +494,15 b' class LoginRequired(object):' | |||||
450 | def __wrapper(self, func, *fargs, **fkwargs): |
|
494 | def __wrapper(self, func, *fargs, **fkwargs): | |
451 | cls = fargs[0] |
|
495 | cls = fargs[0] | |
452 | user = cls.rhodecode_user |
|
496 | user = cls.rhodecode_user | |
|
497 | loc = "%s:%s" % (cls.__class__.__name__, func.__name__) | |||
|
498 | ||||
|
499 | #check IP | |||
|
500 | ip_access_ok = True | |||
|
501 | if not user.ip_allowed: | |||
|
502 | from rhodecode.lib import helpers as h | |||
|
503 | h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))), | |||
|
504 | category='warning') | |||
|
505 | ip_access_ok = False | |||
453 |
|
506 | |||
454 | api_access_ok = False |
|
507 | api_access_ok = False | |
455 | if self.api_access: |
|
508 | if self.api_access: | |
@@ -458,9 +511,9 b' class LoginRequired(object):' | |||||
458 | api_access_ok = True |
|
511 | api_access_ok = True | |
459 | else: |
|
512 | else: | |
460 | log.debug("API KEY token not valid") |
|
513 | log.debug("API KEY token not valid") | |
461 | loc = "%s:%s" % (cls.__class__.__name__, func.__name__) |
|
514 | ||
462 | log.debug('Checking if %s is authenticated @ %s' % (user.username, loc)) |
|
515 | log.debug('Checking if %s is authenticated @ %s' % (user.username, loc)) | |
463 | if user.is_authenticated or api_access_ok: |
|
516 | if (user.is_authenticated or api_access_ok) and ip_access_ok: | |
464 | reason = 'RegularAuth' if user.is_authenticated else 'APIAuth' |
|
517 | reason = 'RegularAuth' if user.is_authenticated else 'APIAuth' | |
465 | log.info('user %s is authenticated and granted access to %s ' |
|
518 | log.info('user %s is authenticated and granted access to %s ' | |
466 | 'using %s' % (user.username, loc, reason) |
|
519 | 'using %s' % (user.username, loc, reason) | |
@@ -682,12 +735,12 b' class PermsFunction(object):' | |||||
682 | return False |
|
735 | return False | |
683 | self.user_perms = user.permissions |
|
736 | self.user_perms = user.permissions | |
684 | if self.check_permissions(): |
|
737 | if self.check_permissions(): | |
685 | log.debug('Permission granted for user: %s @ %s', user, |
|
738 | log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user, | |
686 | check_Location or 'unspecified location') |
|
739 | check_Location or 'unspecified location') | |
687 | return True |
|
740 | return True | |
688 |
|
741 | |||
689 | else: |
|
742 | else: | |
690 | log.debug('Permission denied for user: %s @ %s', user, |
|
743 | log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user, | |
691 | check_Location or 'unspecified location') |
|
744 | check_Location or 'unspecified location') | |
692 | return False |
|
745 | return False | |
693 |
|
746 | |||
@@ -821,3 +874,122 b' class HasPermissionAnyMiddleware(object)' | |||||
821 | ) |
|
874 | ) | |
822 | ) |
|
875 | ) | |
823 | return False |
|
876 | return False | |
|
877 | ||||
|
878 | ||||
|
879 | #============================================================================== | |||
|
880 | # SPECIAL VERSION TO HANDLE API AUTH | |||
|
881 | #============================================================================== | |||
|
882 | class _BaseApiPerm(object): | |||
|
883 | def __init__(self, *perms): | |||
|
884 | self.required_perms = set(perms) | |||
|
885 | ||||
|
886 | def __call__(self, check_location='unspecified', user=None, repo_name=None): | |||
|
887 | cls_name = self.__class__.__name__ | |||
|
888 | check_scope = 'user:%s, repo:%s' % (user, repo_name) | |||
|
889 | log.debug('checking cls:%s %s %s @ %s', cls_name, | |||
|
890 | self.required_perms, check_scope, check_location) | |||
|
891 | if not user: | |||
|
892 | log.debug('Empty User passed into arguments') | |||
|
893 | return False | |||
|
894 | ||||
|
895 | ## process user | |||
|
896 | if not isinstance(user, AuthUser): | |||
|
897 | user = AuthUser(user.user_id) | |||
|
898 | ||||
|
899 | if self.check_permissions(user.permissions, repo_name): | |||
|
900 | log.debug('Permission to %s granted for user: %s @ %s', repo_name, | |||
|
901 | user, check_location) | |||
|
902 | return True | |||
|
903 | ||||
|
904 | else: | |||
|
905 | log.debug('Permission to %s denied for user: %s @ %s', repo_name, | |||
|
906 | user, check_location) | |||
|
907 | return False | |||
|
908 | ||||
|
909 | def check_permissions(self, perm_defs, repo_name): | |||
|
910 | """ | |||
|
911 | implement in child class should return True if permissions are ok, | |||
|
912 | False otherwise | |||
|
913 | ||||
|
914 | :param perm_defs: dict with permission definitions | |||
|
915 | :param repo_name: repo name | |||
|
916 | """ | |||
|
917 | raise NotImplementedError() | |||
|
918 | ||||
|
919 | ||||
|
920 | class HasPermissionAllApi(_BaseApiPerm): | |||
|
921 | def __call__(self, user, check_location=''): | |||
|
922 | return super(HasPermissionAllApi, self)\ | |||
|
923 | .__call__(check_location=check_location, user=user) | |||
|
924 | ||||
|
925 | def check_permissions(self, perm_defs, repo): | |||
|
926 | if self.required_perms.issubset(perm_defs.get('global')): | |||
|
927 | return True | |||
|
928 | return False | |||
|
929 | ||||
|
930 | ||||
|
931 | class HasPermissionAnyApi(_BaseApiPerm): | |||
|
932 | def __call__(self, user, check_location=''): | |||
|
933 | return super(HasPermissionAnyApi, self)\ | |||
|
934 | .__call__(check_location=check_location, user=user) | |||
|
935 | ||||
|
936 | def check_permissions(self, perm_defs, repo): | |||
|
937 | if self.required_perms.intersection(perm_defs.get('global')): | |||
|
938 | return True | |||
|
939 | return False | |||
|
940 | ||||
|
941 | ||||
|
942 | class HasRepoPermissionAllApi(_BaseApiPerm): | |||
|
943 | def __call__(self, user, repo_name, check_location=''): | |||
|
944 | return super(HasRepoPermissionAllApi, self)\ | |||
|
945 | .__call__(check_location=check_location, user=user, | |||
|
946 | repo_name=repo_name) | |||
|
947 | ||||
|
948 | def check_permissions(self, perm_defs, repo_name): | |||
|
949 | ||||
|
950 | try: | |||
|
951 | self._user_perms = set( | |||
|
952 | [perm_defs['repositories'][repo_name]] | |||
|
953 | ) | |||
|
954 | except KeyError: | |||
|
955 | log.warning(traceback.format_exc()) | |||
|
956 | return False | |||
|
957 | if self.required_perms.issubset(self._user_perms): | |||
|
958 | return True | |||
|
959 | return False | |||
|
960 | ||||
|
961 | ||||
|
962 | class HasRepoPermissionAnyApi(_BaseApiPerm): | |||
|
963 | def __call__(self, user, repo_name, check_location=''): | |||
|
964 | return super(HasRepoPermissionAnyApi, self)\ | |||
|
965 | .__call__(check_location=check_location, user=user, | |||
|
966 | repo_name=repo_name) | |||
|
967 | ||||
|
968 | def check_permissions(self, perm_defs, repo_name): | |||
|
969 | ||||
|
970 | try: | |||
|
971 | _user_perms = set( | |||
|
972 | [perm_defs['repositories'][repo_name]] | |||
|
973 | ) | |||
|
974 | except KeyError: | |||
|
975 | log.warning(traceback.format_exc()) | |||
|
976 | return False | |||
|
977 | if self.required_perms.intersection(_user_perms): | |||
|
978 | return True | |||
|
979 | return False | |||
|
980 | ||||
|
981 | ||||
|
982 | def check_ip_access(source_ip, allowed_ips=None): | |||
|
983 | """ | |||
|
984 | Checks if source_ip is a subnet of any of allowed_ips. | |||
|
985 | ||||
|
986 | :param source_ip: | |||
|
987 | :param allowed_ips: list of allowed ips together with mask | |||
|
988 | """ | |||
|
989 | from rhodecode.lib import ipaddr | |||
|
990 | log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips)) | |||
|
991 | if isinstance(allowed_ips, (tuple, list, set)): | |||
|
992 | for ip in allowed_ips: | |||
|
993 | if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip): | |||
|
994 | return True | |||
|
995 | return False |
@@ -37,13 +37,18 b' def _get_ip_addr(environ):' | |||||
37 | proxy_key2 = 'HTTP_X_FORWARDED_FOR' |
|
37 | proxy_key2 = 'HTTP_X_FORWARDED_FOR' | |
38 | def_key = 'REMOTE_ADDR' |
|
38 | def_key = 'REMOTE_ADDR' | |
39 |
|
39 | |||
40 |
ip = environ.get(proxy_key |
|
40 | ip = environ.get(proxy_key) | |
41 | if ip: |
|
41 | if ip: | |
42 | return ip |
|
42 | return ip | |
43 |
|
43 | |||
44 | ip = environ.get(proxy_key) |
|
44 | ip = environ.get(proxy_key2) | |
45 |
|
||||
46 | if ip: |
|
45 | if ip: | |
|
46 | # HTTP_X_FORWARDED_FOR can have mutliple ips inside | |||
|
47 | # the left-most being the original client, and each successive proxy | |||
|
48 | # that passed the request adding the IP address where it received the | |||
|
49 | # request from. | |||
|
50 | if ',' in ip: | |||
|
51 | ip = ip.split(',')[0].strip() | |||
47 | return ip |
|
52 | return ip | |
48 |
|
53 | |||
49 | ip = environ.get(def_key, '0.0.0.0') |
|
54 | ip = environ.get(def_key, '0.0.0.0') | |
@@ -101,7 +106,7 b' class BaseVCSController(object):' | |||||
101 | #authenticate this mercurial request using authfunc |
|
106 | #authenticate this mercurial request using authfunc | |
102 | self.authenticate = BasicAuth('', authfunc, |
|
107 | self.authenticate = BasicAuth('', authfunc, | |
103 | config.get('auth_ret_code')) |
|
108 | config.get('auth_ret_code')) | |
104 | self.ipaddr = '0.0.0.0' |
|
109 | self.ip_addr = '0.0.0.0' | |
105 |
|
110 | |||
106 | def _handle_request(self, environ, start_response): |
|
111 | def _handle_request(self, environ, start_response): | |
107 | raise NotImplementedError() |
|
112 | raise NotImplementedError() | |
@@ -136,7 +141,7 b' class BaseVCSController(object):' | |||||
136 | """ |
|
141 | """ | |
137 | invalidate_cache('get_repo_cached_%s' % repo_name) |
|
142 | invalidate_cache('get_repo_cached_%s' % repo_name) | |
138 |
|
143 | |||
139 | def _check_permission(self, action, user, repo_name): |
|
144 | def _check_permission(self, action, user, repo_name, ip_addr=None): | |
140 | """ |
|
145 | """ | |
141 | Checks permissions using action (push/pull) user and repository |
|
146 | Checks permissions using action (push/pull) user and repository | |
142 | name |
|
147 | name | |
@@ -145,6 +150,12 b' class BaseVCSController(object):' | |||||
145 | :param user: user instance |
|
150 | :param user: user instance | |
146 | :param repo_name: repository name |
|
151 | :param repo_name: repository name | |
147 | """ |
|
152 | """ | |
|
153 | #check IP | |||
|
154 | authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr) | |||
|
155 | if not authuser.ip_allowed: | |||
|
156 | return False | |||
|
157 | else: | |||
|
158 | log.info('Access for IP:%s allowed' % (ip_addr)) | |||
148 | if action == 'push': |
|
159 | if action == 'push': | |
149 | if not HasPermissionAnyMiddleware('repository.write', |
|
160 | if not HasPermissionAnyMiddleware('repository.write', | |
150 | 'repository.admin')(user, |
|
161 | 'repository.admin')(user, | |
@@ -235,6 +246,9 b' class BaseVCSController(object):' | |||||
235 | class BaseController(WSGIController): |
|
246 | class BaseController(WSGIController): | |
236 |
|
247 | |||
237 | def __before__(self): |
|
248 | def __before__(self): | |
|
249 | """ | |||
|
250 | __before__ is called before controller methods and after __call__ | |||
|
251 | """ | |||
238 | c.rhodecode_version = __version__ |
|
252 | c.rhodecode_version = __version__ | |
239 | c.rhodecode_instanceid = config.get('instance_id') |
|
253 | c.rhodecode_instanceid = config.get('instance_id') | |
240 | c.rhodecode_name = config.get('rhodecode_title') |
|
254 | c.rhodecode_name = config.get('rhodecode_title') | |
@@ -258,7 +272,6 b' class BaseController(WSGIController):' | |||||
258 |
|
272 | |||
259 | self.sa = meta.Session |
|
273 | self.sa = meta.Session | |
260 | self.scm_model = ScmModel(self.sa) |
|
274 | self.scm_model = ScmModel(self.sa) | |
261 | self.ip_addr = '' |
|
|||
262 |
|
275 | |||
263 | def __call__(self, environ, start_response): |
|
276 | def __call__(self, environ, start_response): | |
264 | """Invoke the Controller""" |
|
277 | """Invoke the Controller""" | |
@@ -273,7 +286,7 b' class BaseController(WSGIController):' | |||||
273 | cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) |
|
286 | cookie_store = CookieStoreWrapper(session.get('rhodecode_user')) | |
274 | user_id = cookie_store.get('user_id', None) |
|
287 | user_id = cookie_store.get('user_id', None) | |
275 | username = get_container_username(environ, config) |
|
288 | username = get_container_username(environ, config) | |
276 | auth_user = AuthUser(user_id, api_key, username) |
|
289 | auth_user = AuthUser(user_id, api_key, username, self.ip_addr) | |
277 | request.user = auth_user |
|
290 | request.user = auth_user | |
278 | self.rhodecode_user = c.rhodecode_user = auth_user |
|
291 | self.rhodecode_user = c.rhodecode_user = auth_user | |
279 | if not self.rhodecode_user.is_authenticated and \ |
|
292 | if not self.rhodecode_user.is_authenticated and \ | |
@@ -311,7 +324,7 b' class BaseRepoController(BaseController)' | |||||
311 | dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) |
|
324 | dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) | |
312 | c.rhodecode_repo = c.rhodecode_db_repo.scm_instance |
|
325 | c.rhodecode_repo = c.rhodecode_db_repo.scm_instance | |
313 | # update last change according to VCS data |
|
326 | # update last change according to VCS data | |
314 | dbr.update_last_change(c.rhodecode_repo.last_change) |
|
327 | dbr.update_changeset_cache(dbr.get_changeset()) | |
315 | if c.rhodecode_repo is None: |
|
328 | if c.rhodecode_repo is None: | |
316 | log.error('%s this repository is present in database but it ' |
|
329 | log.error('%s this repository is present in database but it ' | |
317 | 'cannot be created as an scm instance', c.repo_name) |
|
330 | 'cannot be created as an scm instance', c.repo_name) |
@@ -347,6 +347,10 b' def send_email(recipients, subject, body' | |||||
347 | debug = str2bool(config.get('debug')) |
|
347 | debug = str2bool(config.get('debug')) | |
348 | smtp_auth = email_config.get('smtp_auth') |
|
348 | smtp_auth = email_config.get('smtp_auth') | |
349 |
|
349 | |||
|
350 | if not mail_server: | |||
|
351 | log.error("SMTP mail server not configured - cannot send mail") | |||
|
352 | return False | |||
|
353 | ||||
350 | try: |
|
354 | try: | |
351 | m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth, |
|
355 | m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth, | |
352 | mail_port, ssl, tls, debug=debug) |
|
356 | mail_port, ssl, tls, debug=debug) |
@@ -164,8 +164,8 b' class DbManage(object):' | |||||
164 |
|
164 | |||
165 | def step_0(self): |
|
165 | def step_0(self): | |
166 | # step 0 is the schema upgrade, and than follow proper upgrades |
|
166 | # step 0 is the schema upgrade, and than follow proper upgrades | |
167 |
notify('attempting to do database upgrade |
|
167 | notify('attempting to do database upgrade from ' | |
168 |
|
|
168 | 'version %s to version %s' %(curr_version, __dbversion__)) | |
169 | api.upgrade(db_uri, repository_path, __dbversion__) |
|
169 | api.upgrade(db_uri, repository_path, __dbversion__) | |
170 | notify('Schema upgrade completed') |
|
170 | notify('Schema upgrade completed') | |
171 |
|
171 | |||
@@ -286,6 +286,9 b' class DbManage(object):' | |||||
286 | 'Please validate and check default permissions ' |
|
286 | 'Please validate and check default permissions ' | |
287 | 'in admin panel') |
|
287 | 'in admin panel') | |
288 |
|
288 | |||
|
289 | def step_10(self): | |||
|
290 | pass | |||
|
291 | ||||
289 | upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) |
|
292 | upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) | |
290 |
|
293 | |||
291 | # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE |
|
294 | # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE |
@@ -619,7 +619,7 b' class Repository(Base, BaseModel):' | |||||
619 | hg_ui = ret |
|
619 | hg_ui = ret | |
620 | for ui_ in hg_ui: |
|
620 | for ui_ in hg_ui: | |
621 | if ui_.ui_active: |
|
621 | if ui_.ui_active: | |
622 |
log.debug('settings ui from db[%s]%s |
|
622 | log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section, | |
623 | ui_.ui_key, ui_.ui_value) |
|
623 | ui_.ui_key, ui_.ui_value) | |
624 | baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) |
|
624 | baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) | |
625 |
|
625 |
@@ -623,7 +623,7 b' class Repository(Base, BaseModel):' | |||||
623 | hg_ui = ret |
|
623 | hg_ui = ret | |
624 | for ui_ in hg_ui: |
|
624 | for ui_ in hg_ui: | |
625 | if ui_.ui_active: |
|
625 | if ui_.ui_active: | |
626 |
log.debug('settings ui from db[%s]%s |
|
626 | log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section, | |
627 | ui_.ui_key, ui_.ui_value) |
|
627 | ui_.ui_key, ui_.ui_value) | |
628 | baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) |
|
628 | baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) | |
629 |
|
629 |
This diff has been collapsed as it changes many lines, (1814 lines changed) Show them Hide them | |||||
@@ -1,9 +1,9 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """ |
|
2 | """ | |
3 |
rhodecode.model.db_1_ |
|
3 | rhodecode.model.db_1_5_0 | |
4 | ~~~~~~~~~~~~~~~~~~~~~~~~ |
|
4 | ~~~~~~~~~~~~~~~~~~~~~~~~ | |
5 |
|
5 | |||
6 |
Database Models for RhodeCode <=1.5. |
|
6 | Database Models for RhodeCode <=1.5.2 | |
7 |
|
7 | |||
8 | :created_on: Apr 08, 2010 |
|
8 | :created_on: Apr 08, 2010 | |
9 | :author: marcink |
|
9 | :author: marcink | |
@@ -23,6 +23,1812 b'' | |||||
23 | # You should have received a copy of the GNU General Public License |
|
23 | # You should have received a copy of the GNU General Public License | |
24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
25 |
|
25 | |||
26 | #TODO: replace that will db.py content after 1.6 Release |
|
26 | import os | |
|
27 | import logging | |||
|
28 | import datetime | |||
|
29 | import traceback | |||
|
30 | import hashlib | |||
|
31 | import time | |||
|
32 | from collections import defaultdict | |||
|
33 | ||||
|
34 | from sqlalchemy import * | |||
|
35 | from sqlalchemy.ext.hybrid import hybrid_property | |||
|
36 | from sqlalchemy.orm import relationship, joinedload, class_mapper, validates | |||
|
37 | from sqlalchemy.exc import DatabaseError | |||
|
38 | from beaker.cache import cache_region, region_invalidate | |||
|
39 | from webob.exc import HTTPNotFound | |||
|
40 | ||||
|
41 | from pylons.i18n.translation import lazy_ugettext as _ | |||
|
42 | ||||
|
43 | from rhodecode.lib.vcs import get_backend | |||
|
44 | from rhodecode.lib.vcs.utils.helpers import get_scm | |||
|
45 | from rhodecode.lib.vcs.exceptions import VCSError | |||
|
46 | from rhodecode.lib.vcs.utils.lazy import LazyProperty | |||
|
47 | ||||
|
48 | from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ | |||
|
49 | safe_unicode, remove_suffix, remove_prefix | |||
|
50 | from rhodecode.lib.compat import json | |||
|
51 | from rhodecode.lib.caching_query import FromCache | |||
|
52 | ||||
|
53 | from rhodecode.model.meta import Base, Session | |||
|
54 | ||||
|
55 | URL_SEP = '/' | |||
|
56 | log = logging.getLogger(__name__) | |||
|
57 | ||||
|
58 | #============================================================================== | |||
|
59 | # BASE CLASSES | |||
|
60 | #============================================================================== | |||
|
61 | ||||
|
62 | _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() | |||
|
63 | ||||
|
64 | ||||
|
65 | class BaseModel(object): | |||
|
66 | """ | |||
|
67 | Base Model for all classess | |||
|
68 | """ | |||
|
69 | ||||
|
70 | @classmethod | |||
|
71 | def _get_keys(cls): | |||
|
72 | """return column names for this model """ | |||
|
73 | return class_mapper(cls).c.keys() | |||
|
74 | ||||
|
75 | def get_dict(self): | |||
|
76 | """ | |||
|
77 | return dict with keys and values corresponding | |||
|
78 | to this model data """ | |||
|
79 | ||||
|
80 | d = {} | |||
|
81 | for k in self._get_keys(): | |||
|
82 | d[k] = getattr(self, k) | |||
|
83 | ||||
|
84 | # also use __json__() if present to get additional fields | |||
|
85 | _json_attr = getattr(self, '__json__', None) | |||
|
86 | if _json_attr: | |||
|
87 | # update with attributes from __json__ | |||
|
88 | if callable(_json_attr): | |||
|
89 | _json_attr = _json_attr() | |||
|
90 | for k, val in _json_attr.iteritems(): | |||
|
91 | d[k] = val | |||
|
92 | return d | |||
|
93 | ||||
|
94 | def get_appstruct(self): | |||
|
95 | """return list with keys and values tupples corresponding | |||
|
96 | to this model data """ | |||
|
97 | ||||
|
98 | l = [] | |||
|
99 | for k in self._get_keys(): | |||
|
100 | l.append((k, getattr(self, k),)) | |||
|
101 | return l | |||
|
102 | ||||
|
103 | def populate_obj(self, populate_dict): | |||
|
104 | """populate model with data from given populate_dict""" | |||
|
105 | ||||
|
106 | for k in self._get_keys(): | |||
|
107 | if k in populate_dict: | |||
|
108 | setattr(self, k, populate_dict[k]) | |||
|
109 | ||||
|
110 | @classmethod | |||
|
111 | def query(cls): | |||
|
112 | return Session().query(cls) | |||
|
113 | ||||
|
114 | @classmethod | |||
|
115 | def get(cls, id_): | |||
|
116 | if id_: | |||
|
117 | return cls.query().get(id_) | |||
|
118 | ||||
|
119 | @classmethod | |||
|
120 | def get_or_404(cls, id_): | |||
|
121 | try: | |||
|
122 | id_ = int(id_) | |||
|
123 | except (TypeError, ValueError): | |||
|
124 | raise HTTPNotFound | |||
|
125 | ||||
|
126 | res = cls.query().get(id_) | |||
|
127 | if not res: | |||
|
128 | raise HTTPNotFound | |||
|
129 | return res | |||
|
130 | ||||
|
131 | @classmethod | |||
|
132 | def getAll(cls): | |||
|
133 | return cls.query().all() | |||
|
134 | ||||
|
135 | @classmethod | |||
|
136 | def delete(cls, id_): | |||
|
137 | obj = cls.query().get(id_) | |||
|
138 | Session().delete(obj) | |||
|
139 | ||||
|
140 | def __repr__(self): | |||
|
141 | if hasattr(self, '__unicode__'): | |||
|
142 | # python repr needs to return str | |||
|
143 | return safe_str(self.__unicode__()) | |||
|
144 | return '<DB:%s>' % (self.__class__.__name__) | |||
|
145 | ||||
|
146 | ||||
|
147 | class RhodeCodeSetting(Base, BaseModel): | |||
|
148 | __tablename__ = 'rhodecode_settings' | |||
|
149 | __table_args__ = ( | |||
|
150 | UniqueConstraint('app_settings_name'), | |||
|
151 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
152 | 'mysql_charset': 'utf8'} | |||
|
153 | ) | |||
|
154 | app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
155 | app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
156 | _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
157 | ||||
|
158 | def __init__(self, k='', v=''): | |||
|
159 | self.app_settings_name = k | |||
|
160 | self.app_settings_value = v | |||
|
161 | ||||
|
162 | @validates('_app_settings_value') | |||
|
163 | def validate_settings_value(self, key, val): | |||
|
164 | assert type(val) == unicode | |||
|
165 | return val | |||
|
166 | ||||
|
167 | @hybrid_property | |||
|
168 | def app_settings_value(self): | |||
|
169 | v = self._app_settings_value | |||
|
170 | if self.app_settings_name in ["ldap_active", | |||
|
171 | "default_repo_enable_statistics", | |||
|
172 | "default_repo_enable_locking", | |||
|
173 | "default_repo_private", | |||
|
174 | "default_repo_enable_downloads"]: | |||
|
175 | v = str2bool(v) | |||
|
176 | return v | |||
|
177 | ||||
|
178 | @app_settings_value.setter | |||
|
179 | def app_settings_value(self, val): | |||
|
180 | """ | |||
|
181 | Setter that will always make sure we use unicode in app_settings_value | |||
|
182 | ||||
|
183 | :param val: | |||
|
184 | """ | |||
|
185 | self._app_settings_value = safe_unicode(val) | |||
|
186 | ||||
|
187 | def __unicode__(self): | |||
|
188 | return u"<%s('%s:%s')>" % ( | |||
|
189 | self.__class__.__name__, | |||
|
190 | self.app_settings_name, self.app_settings_value | |||
|
191 | ) | |||
|
192 | ||||
|
193 | @classmethod | |||
|
194 | def get_by_name(cls, key): | |||
|
195 | return cls.query()\ | |||
|
196 | .filter(cls.app_settings_name == key).scalar() | |||
|
197 | ||||
|
198 | @classmethod | |||
|
199 | def get_by_name_or_create(cls, key): | |||
|
200 | res = cls.get_by_name(key) | |||
|
201 | if not res: | |||
|
202 | res = cls(key) | |||
|
203 | return res | |||
|
204 | ||||
|
205 | @classmethod | |||
|
206 | def get_app_settings(cls, cache=False): | |||
|
207 | ||||
|
208 | ret = cls.query() | |||
|
209 | ||||
|
210 | if cache: | |||
|
211 | ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) | |||
|
212 | ||||
|
213 | if not ret: | |||
|
214 | raise Exception('Could not get application settings !') | |||
|
215 | settings = {} | |||
|
216 | for each in ret: | |||
|
217 | settings['rhodecode_' + each.app_settings_name] = \ | |||
|
218 | each.app_settings_value | |||
|
219 | ||||
|
220 | return settings | |||
|
221 | ||||
|
222 | @classmethod | |||
|
223 | def get_ldap_settings(cls, cache=False): | |||
|
224 | ret = cls.query()\ | |||
|
225 | .filter(cls.app_settings_name.startswith('ldap_')).all() | |||
|
226 | fd = {} | |||
|
227 | for row in ret: | |||
|
228 | fd.update({row.app_settings_name: row.app_settings_value}) | |||
|
229 | ||||
|
230 | return fd | |||
|
231 | ||||
|
232 | @classmethod | |||
|
233 | def get_default_repo_settings(cls, cache=False, strip_prefix=False): | |||
|
234 | ret = cls.query()\ | |||
|
235 | .filter(cls.app_settings_name.startswith('default_')).all() | |||
|
236 | fd = {} | |||
|
237 | for row in ret: | |||
|
238 | key = row.app_settings_name | |||
|
239 | if strip_prefix: | |||
|
240 | key = remove_prefix(key, prefix='default_') | |||
|
241 | fd.update({key: row.app_settings_value}) | |||
|
242 | ||||
|
243 | return fd | |||
|
244 | ||||
|
245 | ||||
|
246 | class RhodeCodeUi(Base, BaseModel): | |||
|
247 | __tablename__ = 'rhodecode_ui' | |||
|
248 | __table_args__ = ( | |||
|
249 | UniqueConstraint('ui_key'), | |||
|
250 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
251 | 'mysql_charset': 'utf8'} | |||
|
252 | ) | |||
|
253 | ||||
|
254 | HOOK_UPDATE = 'changegroup.update' | |||
|
255 | HOOK_REPO_SIZE = 'changegroup.repo_size' | |||
|
256 | HOOK_PUSH = 'changegroup.push_logger' | |||
|
257 | HOOK_PRE_PUSH = 'prechangegroup.pre_push' | |||
|
258 | HOOK_PULL = 'outgoing.pull_logger' | |||
|
259 | HOOK_PRE_PULL = 'preoutgoing.pre_pull' | |||
|
260 | ||||
|
261 | ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
262 | ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
263 | ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
264 | ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
265 | ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) | |||
|
266 | ||||
|
267 | @classmethod | |||
|
268 | def get_by_key(cls, key): | |||
|
269 | return cls.query().filter(cls.ui_key == key).scalar() | |||
|
270 | ||||
|
271 | @classmethod | |||
|
272 | def get_builtin_hooks(cls): | |||
|
273 | q = cls.query() | |||
|
274 | q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, | |||
|
275 | cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, | |||
|
276 | cls.HOOK_PULL, cls.HOOK_PRE_PULL])) | |||
|
277 | return q.all() | |||
|
278 | ||||
|
279 | @classmethod | |||
|
280 | def get_custom_hooks(cls): | |||
|
281 | q = cls.query() | |||
|
282 | q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, | |||
|
283 | cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, | |||
|
284 | cls.HOOK_PULL, cls.HOOK_PRE_PULL])) | |||
|
285 | q = q.filter(cls.ui_section == 'hooks') | |||
|
286 | return q.all() | |||
|
287 | ||||
|
288 | @classmethod | |||
|
289 | def get_repos_location(cls): | |||
|
290 | return cls.get_by_key('/').ui_value | |||
|
291 | ||||
|
292 | @classmethod | |||
|
293 | def create_or_update_hook(cls, key, val): | |||
|
294 | new_ui = cls.get_by_key(key) or cls() | |||
|
295 | new_ui.ui_section = 'hooks' | |||
|
296 | new_ui.ui_active = True | |||
|
297 | new_ui.ui_key = key | |||
|
298 | new_ui.ui_value = val | |||
|
299 | ||||
|
300 | Session().add(new_ui) | |||
|
301 | ||||
|
302 | def __repr__(self): | |||
|
303 | return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key, | |||
|
304 | self.ui_value) | |||
|
305 | ||||
|
306 | ||||
|
307 | class User(Base, BaseModel): | |||
|
308 | __tablename__ = 'users' | |||
|
309 | __table_args__ = ( | |||
|
310 | UniqueConstraint('username'), UniqueConstraint('email'), | |||
|
311 | Index('u_username_idx', 'username'), | |||
|
312 | Index('u_email_idx', 'email'), | |||
|
313 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
314 | 'mysql_charset': 'utf8'} | |||
|
315 | ) | |||
|
316 | DEFAULT_USER = 'default' | |||
|
317 | DEFAULT_PERMISSIONS = [ | |||
|
318 | 'hg.register.manual_activate', 'hg.create.repository', | |||
|
319 | 'hg.fork.repository', 'repository.read', 'group.read' | |||
|
320 | ] | |||
|
321 | user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
322 | username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
323 | password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
324 | active = Column("active", Boolean(), nullable=True, unique=None, default=True) | |||
|
325 | admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) | |||
|
326 | name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
327 | lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
328 | _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
329 | last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) | |||
|
330 | ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
331 | api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
332 | inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) | |||
|
333 | ||||
|
334 | user_log = relationship('UserLog') | |||
|
335 | user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') | |||
|
336 | ||||
|
337 | repositories = relationship('Repository') | |||
|
338 | user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') | |||
|
339 | followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') | |||
|
340 | ||||
|
341 | repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') | |||
|
342 | repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') | |||
|
343 | ||||
|
344 | group_member = relationship('UsersGroupMember', cascade='all') | |||
|
345 | ||||
|
346 | notifications = relationship('UserNotification', cascade='all') | |||
|
347 | # notifications assigned to this user | |||
|
348 | user_created_notifications = relationship('Notification', cascade='all') | |||
|
349 | # comments created by this user | |||
|
350 | user_comments = relationship('ChangesetComment', cascade='all') | |||
|
351 | #extra emails for this user | |||
|
352 | user_emails = relationship('UserEmailMap', cascade='all') | |||
|
353 | ||||
|
354 | @hybrid_property | |||
|
355 | def email(self): | |||
|
356 | return self._email | |||
|
357 | ||||
|
358 | @email.setter | |||
|
359 | def email(self, val): | |||
|
360 | self._email = val.lower() if val else None | |||
|
361 | ||||
|
362 | @property | |||
|
363 | def firstname(self): | |||
|
364 | # alias for future | |||
|
365 | return self.name | |||
|
366 | ||||
|
367 | @property | |||
|
368 | def emails(self): | |||
|
369 | other = UserEmailMap.query().filter(UserEmailMap.user==self).all() | |||
|
370 | return [self.email] + [x.email for x in other] | |||
|
371 | ||||
|
372 | @property | |||
|
373 | def username_and_name(self): | |||
|
374 | return '%s (%s %s)' % (self.username, self.firstname, self.lastname) | |||
|
375 | ||||
|
376 | @property | |||
|
377 | def full_name(self): | |||
|
378 | return '%s %s' % (self.firstname, self.lastname) | |||
|
379 | ||||
|
380 | @property | |||
|
381 | def full_name_or_username(self): | |||
|
382 | return ('%s %s' % (self.firstname, self.lastname) | |||
|
383 | if (self.firstname and self.lastname) else self.username) | |||
|
384 | ||||
|
385 | @property | |||
|
386 | def full_contact(self): | |||
|
387 | return '%s %s <%s>' % (self.firstname, self.lastname, self.email) | |||
|
388 | ||||
|
389 | @property | |||
|
390 | def short_contact(self): | |||
|
391 | return '%s %s' % (self.firstname, self.lastname) | |||
|
392 | ||||
|
393 | @property | |||
|
394 | def is_admin(self): | |||
|
395 | return self.admin | |||
|
396 | ||||
|
397 | def __unicode__(self): | |||
|
398 | return u"<%s('id:%s:%s')>" % (self.__class__.__name__, | |||
|
399 | self.user_id, self.username) | |||
|
400 | ||||
|
401 | @classmethod | |||
|
402 | def get_by_username(cls, username, case_insensitive=False, cache=False): | |||
|
403 | if case_insensitive: | |||
|
404 | q = cls.query().filter(cls.username.ilike(username)) | |||
|
405 | else: | |||
|
406 | q = cls.query().filter(cls.username == username) | |||
|
407 | ||||
|
408 | if cache: | |||
|
409 | q = q.options(FromCache( | |||
|
410 | "sql_cache_short", | |||
|
411 | "get_user_%s" % _hash_key(username) | |||
|
412 | ) | |||
|
413 | ) | |||
|
414 | return q.scalar() | |||
|
415 | ||||
|
416 | @classmethod | |||
|
417 | def get_by_api_key(cls, api_key, cache=False): | |||
|
418 | q = cls.query().filter(cls.api_key == api_key) | |||
|
419 | ||||
|
420 | if cache: | |||
|
421 | q = q.options(FromCache("sql_cache_short", | |||
|
422 | "get_api_key_%s" % api_key)) | |||
|
423 | return q.scalar() | |||
|
424 | ||||
|
425 | @classmethod | |||
|
426 | def get_by_email(cls, email, case_insensitive=False, cache=False): | |||
|
427 | if case_insensitive: | |||
|
428 | q = cls.query().filter(cls.email.ilike(email)) | |||
|
429 | else: | |||
|
430 | q = cls.query().filter(cls.email == email) | |||
|
431 | ||||
|
432 | if cache: | |||
|
433 | q = q.options(FromCache("sql_cache_short", | |||
|
434 | "get_email_key_%s" % email)) | |||
|
435 | ||||
|
436 | ret = q.scalar() | |||
|
437 | if ret is None: | |||
|
438 | q = UserEmailMap.query() | |||
|
439 | # try fetching in alternate email map | |||
|
440 | if case_insensitive: | |||
|
441 | q = q.filter(UserEmailMap.email.ilike(email)) | |||
|
442 | else: | |||
|
443 | q = q.filter(UserEmailMap.email == email) | |||
|
444 | q = q.options(joinedload(UserEmailMap.user)) | |||
|
445 | if cache: | |||
|
446 | q = q.options(FromCache("sql_cache_short", | |||
|
447 | "get_email_map_key_%s" % email)) | |||
|
448 | ret = getattr(q.scalar(), 'user', None) | |||
|
449 | ||||
|
450 | return ret | |||
|
451 | ||||
|
452 | def update_lastlogin(self): | |||
|
453 | """Update user lastlogin""" | |||
|
454 | self.last_login = datetime.datetime.now() | |||
|
455 | Session().add(self) | |||
|
456 | log.debug('updated user %s lastlogin' % self.username) | |||
|
457 | ||||
|
458 | def get_api_data(self): | |||
|
459 | """ | |||
|
460 | Common function for generating user related data for API | |||
|
461 | """ | |||
|
462 | user = self | |||
|
463 | data = dict( | |||
|
464 | user_id=user.user_id, | |||
|
465 | username=user.username, | |||
|
466 | firstname=user.name, | |||
|
467 | lastname=user.lastname, | |||
|
468 | email=user.email, | |||
|
469 | emails=user.emails, | |||
|
470 | api_key=user.api_key, | |||
|
471 | active=user.active, | |||
|
472 | admin=user.admin, | |||
|
473 | ldap_dn=user.ldap_dn, | |||
|
474 | last_login=user.last_login, | |||
|
475 | ) | |||
|
476 | return data | |||
|
477 | ||||
|
478 | def __json__(self): | |||
|
479 | data = dict( | |||
|
480 | full_name=self.full_name, | |||
|
481 | full_name_or_username=self.full_name_or_username, | |||
|
482 | short_contact=self.short_contact, | |||
|
483 | full_contact=self.full_contact | |||
|
484 | ) | |||
|
485 | data.update(self.get_api_data()) | |||
|
486 | return data | |||
|
487 | ||||
|
488 | ||||
|
489 | class UserEmailMap(Base, BaseModel): | |||
|
490 | __tablename__ = 'user_email_map' | |||
|
491 | __table_args__ = ( | |||
|
492 | Index('uem_email_idx', 'email'), | |||
|
493 | UniqueConstraint('email'), | |||
|
494 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
495 | 'mysql_charset': 'utf8'} | |||
|
496 | ) | |||
|
497 | __mapper_args__ = {} | |||
|
498 | ||||
|
499 | email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
500 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |||
|
501 | _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |||
|
502 | user = relationship('User', lazy='joined') | |||
|
503 | ||||
|
504 | @validates('_email') | |||
|
505 | def validate_email(self, key, email): | |||
|
506 | # check if this email is not main one | |||
|
507 | main_email = Session().query(User).filter(User.email == email).scalar() | |||
|
508 | if main_email is not None: | |||
|
509 | raise AttributeError('email %s is present is user table' % email) | |||
|
510 | return email | |||
|
511 | ||||
|
512 | @hybrid_property | |||
|
513 | def email(self): | |||
|
514 | return self._email | |||
|
515 | ||||
|
516 | @email.setter | |||
|
517 | def email(self, val): | |||
|
518 | self._email = val.lower() if val else None | |||
|
519 | ||||
|
520 | ||||
|
521 | class UserLog(Base, BaseModel): | |||
|
522 | __tablename__ = 'user_logs' | |||
|
523 | __table_args__ = ( | |||
|
524 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
525 | 'mysql_charset': 'utf8'}, | |||
|
526 | ) | |||
|
527 | user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
528 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |||
|
529 | username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
530 | repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) | |||
|
531 | repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
532 | user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
533 | action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
534 | action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) | |||
|
535 | ||||
|
536 | @property | |||
|
537 | def action_as_day(self): | |||
|
538 | return datetime.date(*self.action_date.timetuple()[:3]) | |||
|
539 | ||||
|
540 | user = relationship('User') | |||
|
541 | repository = relationship('Repository', cascade='') | |||
|
542 | ||||
|
543 | ||||
|
544 | class UsersGroup(Base, BaseModel): | |||
|
545 | __tablename__ = 'users_groups' | |||
|
546 | __table_args__ = ( | |||
|
547 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
548 | 'mysql_charset': 'utf8'}, | |||
|
549 | ) | |||
|
550 | ||||
|
551 | users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
552 | users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |||
|
553 | users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) | |||
|
554 | inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) | |||
|
555 | ||||
|
556 | members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined") | |||
|
557 | users_group_to_perm = relationship('UsersGroupToPerm', cascade='all') | |||
|
558 | users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all') | |||
|
559 | ||||
|
560 | def __unicode__(self): | |||
|
561 | return u'<userGroup(%s)>' % (self.users_group_name) | |||
|
562 | ||||
|
563 | @classmethod | |||
|
564 | def get_by_group_name(cls, group_name, cache=False, | |||
|
565 | case_insensitive=False): | |||
|
566 | if case_insensitive: | |||
|
567 | q = cls.query().filter(cls.users_group_name.ilike(group_name)) | |||
|
568 | else: | |||
|
569 | q = cls.query().filter(cls.users_group_name == group_name) | |||
|
570 | if cache: | |||
|
571 | q = q.options(FromCache( | |||
|
572 | "sql_cache_short", | |||
|
573 | "get_user_%s" % _hash_key(group_name) | |||
|
574 | ) | |||
|
575 | ) | |||
|
576 | return q.scalar() | |||
|
577 | ||||
|
578 | @classmethod | |||
|
579 | def get(cls, users_group_id, cache=False): | |||
|
580 | users_group = cls.query() | |||
|
581 | if cache: | |||
|
582 | users_group = users_group.options(FromCache("sql_cache_short", | |||
|
583 | "get_users_group_%s" % users_group_id)) | |||
|
584 | return users_group.get(users_group_id) | |||
|
585 | ||||
|
586 | def get_api_data(self): | |||
|
587 | users_group = self | |||
|
588 | ||||
|
589 | data = dict( | |||
|
590 | users_group_id=users_group.users_group_id, | |||
|
591 | group_name=users_group.users_group_name, | |||
|
592 | active=users_group.users_group_active, | |||
|
593 | ) | |||
|
594 | ||||
|
595 | return data | |||
|
596 | ||||
|
597 | ||||
|
598 | class UsersGroupMember(Base, BaseModel): | |||
|
599 | __tablename__ = 'users_groups_members' | |||
|
600 | __table_args__ = ( | |||
|
601 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
602 | 'mysql_charset': 'utf8'}, | |||
|
603 | ) | |||
|
604 | ||||
|
605 | users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
606 | users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |||
|
607 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |||
|
608 | ||||
|
609 | user = relationship('User', lazy='joined') | |||
|
610 | users_group = relationship('UsersGroup') | |||
|
611 | ||||
|
612 | def __init__(self, gr_id='', u_id=''): | |||
|
613 | self.users_group_id = gr_id | |||
|
614 | self.user_id = u_id | |||
|
615 | ||||
|
616 | ||||
|
617 | class Repository(Base, BaseModel): | |||
|
618 | __tablename__ = 'repositories' | |||
|
619 | __table_args__ = ( | |||
|
620 | UniqueConstraint('repo_name'), | |||
|
621 | Index('r_repo_name_idx', 'repo_name'), | |||
|
622 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
623 | 'mysql_charset': 'utf8'}, | |||
|
624 | ) | |||
|
625 | ||||
|
626 | repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
627 | repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |||
|
628 | clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |||
|
629 | repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |||
|
630 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) | |||
|
631 | private = Column("private", Boolean(), nullable=True, unique=None, default=None) | |||
|
632 | enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) | |||
|
633 | enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) | |||
|
634 | description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
635 | created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |||
|
636 | updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |||
|
637 | landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |||
|
638 | enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) | |||
|
639 | _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |||
|
640 | ||||
|
641 | fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) | |||
|
642 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) | |||
|
643 | ||||
|
644 | user = relationship('User') | |||
|
645 | fork = relationship('Repository', remote_side=repo_id) | |||
|
646 | group = relationship('RepoGroup') | |||
|
647 | repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') | |||
|
648 | users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all') | |||
|
649 | stats = relationship('Statistics', cascade='all', uselist=False) | |||
|
650 | ||||
|
651 | followers = relationship('UserFollowing', | |||
|
652 | primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', | |||
|
653 | cascade='all') | |||
|
654 | ||||
|
655 | logs = relationship('UserLog') | |||
|
656 | comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") | |||
|
657 | ||||
|
658 | pull_requests_org = relationship('PullRequest', | |||
|
659 | primaryjoin='PullRequest.org_repo_id==Repository.repo_id', | |||
|
660 | cascade="all, delete, delete-orphan") | |||
|
661 | ||||
|
662 | pull_requests_other = relationship('PullRequest', | |||
|
663 | primaryjoin='PullRequest.other_repo_id==Repository.repo_id', | |||
|
664 | cascade="all, delete, delete-orphan") | |||
|
665 | ||||
|
666 | def __unicode__(self): | |||
|
667 | return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, | |||
|
668 | self.repo_name) | |||
|
669 | ||||
|
670 | @hybrid_property | |||
|
671 | def locked(self): | |||
|
672 | # always should return [user_id, timelocked] | |||
|
673 | if self._locked: | |||
|
674 | _lock_info = self._locked.split(':') | |||
|
675 | return int(_lock_info[0]), _lock_info[1] | |||
|
676 | return [None, None] | |||
|
677 | ||||
|
678 | @locked.setter | |||
|
679 | def locked(self, val): | |||
|
680 | if val and isinstance(val, (list, tuple)): | |||
|
681 | self._locked = ':'.join(map(str, val)) | |||
|
682 | else: | |||
|
683 | self._locked = None | |||
|
684 | ||||
|
685 | @classmethod | |||
|
686 | def url_sep(cls): | |||
|
687 | return URL_SEP | |||
|
688 | ||||
|
689 | @classmethod | |||
|
690 | def get_by_repo_name(cls, repo_name): | |||
|
691 | q = Session().query(cls).filter(cls.repo_name == repo_name) | |||
|
692 | q = q.options(joinedload(Repository.fork))\ | |||
|
693 | .options(joinedload(Repository.user))\ | |||
|
694 | .options(joinedload(Repository.group)) | |||
|
695 | return q.scalar() | |||
|
696 | ||||
|
697 | @classmethod | |||
|
698 | def get_by_full_path(cls, repo_full_path): | |||
|
699 | repo_name = repo_full_path.split(cls.base_path(), 1)[-1] | |||
|
700 | return cls.get_by_repo_name(repo_name.strip(URL_SEP)) | |||
|
701 | ||||
|
702 | @classmethod | |||
|
703 | def get_repo_forks(cls, repo_id): | |||
|
704 | return cls.query().filter(Repository.fork_id == repo_id) | |||
|
705 | ||||
|
706 | @classmethod | |||
|
707 | def base_path(cls): | |||
|
708 | """ | |||
|
709 | Returns base path when all repos are stored | |||
|
710 | ||||
|
711 | :param cls: | |||
|
712 | """ | |||
|
713 | q = Session().query(RhodeCodeUi)\ | |||
|
714 | .filter(RhodeCodeUi.ui_key == cls.url_sep()) | |||
|
715 | q = q.options(FromCache("sql_cache_short", "repository_repo_path")) | |||
|
716 | return q.one().ui_value | |||
|
717 | ||||
|
718 | @property | |||
|
719 | def forks(self): | |||
|
720 | """ | |||
|
721 | Return forks of this repo | |||
|
722 | """ | |||
|
723 | return Repository.get_repo_forks(self.repo_id) | |||
|
724 | ||||
|
725 | @property | |||
|
726 | def parent(self): | |||
|
727 | """ | |||
|
728 | Returns fork parent | |||
|
729 | """ | |||
|
730 | return self.fork | |||
|
731 | ||||
|
732 | @property | |||
|
733 | def just_name(self): | |||
|
734 | return self.repo_name.split(Repository.url_sep())[-1] | |||
|
735 | ||||
|
736 | @property | |||
|
737 | def groups_with_parents(self): | |||
|
738 | groups = [] | |||
|
739 | if self.group is None: | |||
|
740 | return groups | |||
|
741 | ||||
|
742 | cur_gr = self.group | |||
|
743 | groups.insert(0, cur_gr) | |||
|
744 | while 1: | |||
|
745 | gr = getattr(cur_gr, 'parent_group', None) | |||
|
746 | cur_gr = cur_gr.parent_group | |||
|
747 | if gr is None: | |||
|
748 | break | |||
|
749 | groups.insert(0, gr) | |||
|
750 | ||||
|
751 | return groups | |||
|
752 | ||||
|
753 | @property | |||
|
754 | def groups_and_repo(self): | |||
|
755 | return self.groups_with_parents, self.just_name | |||
|
756 | ||||
|
757 | @LazyProperty | |||
|
758 | def repo_path(self): | |||
|
759 | """ | |||
|
760 | Returns base full path for that repository means where it actually | |||
|
761 | exists on a filesystem | |||
|
762 | """ | |||
|
763 | q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == | |||
|
764 | Repository.url_sep()) | |||
|
765 | q = q.options(FromCache("sql_cache_short", "repository_repo_path")) | |||
|
766 | return q.one().ui_value | |||
|
767 | ||||
|
768 | @property | |||
|
769 | def repo_full_path(self): | |||
|
770 | p = [self.repo_path] | |||
|
771 | # we need to split the name by / since this is how we store the | |||
|
772 | # names in the database, but that eventually needs to be converted | |||
|
773 | # into a valid system path | |||
|
774 | p += self.repo_name.split(Repository.url_sep()) | |||
|
775 | return os.path.join(*p) | |||
|
776 | ||||
|
777 | @property | |||
|
778 | def cache_keys(self): | |||
|
779 | """ | |||
|
780 | Returns associated cache keys for that repo | |||
|
781 | """ | |||
|
782 | return CacheInvalidation.query()\ | |||
|
783 | .filter(CacheInvalidation.cache_args == self.repo_name)\ | |||
|
784 | .order_by(CacheInvalidation.cache_key)\ | |||
|
785 | .all() | |||
|
786 | ||||
|
787 | def get_new_name(self, repo_name): | |||
|
788 | """ | |||
|
789 | returns new full repository name based on assigned group and new new | |||
|
790 | ||||
|
791 | :param group_name: | |||
|
792 | """ | |||
|
793 | path_prefix = self.group.full_path_splitted if self.group else [] | |||
|
794 | return Repository.url_sep().join(path_prefix + [repo_name]) | |||
|
795 | ||||
|
796 | @property | |||
|
797 | def _ui(self): | |||
|
798 | """ | |||
|
799 | Creates an db based ui object for this repository | |||
|
800 | """ | |||
|
801 | from rhodecode.lib.utils import make_ui | |||
|
802 | return make_ui('db', clear_session=False) | |||
|
803 | ||||
|
804 | @classmethod | |||
|
805 | def inject_ui(cls, repo, extras={}): | |||
|
806 | from rhodecode.lib.vcs.backends.hg import MercurialRepository | |||
|
807 | from rhodecode.lib.vcs.backends.git import GitRepository | |||
|
808 | required = (MercurialRepository, GitRepository) | |||
|
809 | if not isinstance(repo, required): | |||
|
810 | raise Exception('repo must be instance of %s' % required) | |||
|
811 | ||||
|
812 | # inject ui extra param to log this action via push logger | |||
|
813 | for k, v in extras.items(): | |||
|
814 | repo._repo.ui.setconfig('rhodecode_extras', k, v) | |||
|
815 | ||||
|
816 | @classmethod | |||
|
817 | def is_valid(cls, repo_name): | |||
|
818 | """ | |||
|
819 | returns True if given repo name is a valid filesystem repository | |||
|
820 | ||||
|
821 | :param cls: | |||
|
822 | :param repo_name: | |||
|
823 | """ | |||
|
824 | from rhodecode.lib.utils import is_valid_repo | |||
|
825 | ||||
|
826 | return is_valid_repo(repo_name, cls.base_path()) | |||
|
827 | ||||
|
828 | def get_api_data(self): | |||
|
829 | """ | |||
|
830 | Common function for generating repo api data | |||
|
831 | ||||
|
832 | """ | |||
|
833 | repo = self | |||
|
834 | data = dict( | |||
|
835 | repo_id=repo.repo_id, | |||
|
836 | repo_name=repo.repo_name, | |||
|
837 | repo_type=repo.repo_type, | |||
|
838 | clone_uri=repo.clone_uri, | |||
|
839 | private=repo.private, | |||
|
840 | created_on=repo.created_on, | |||
|
841 | description=repo.description, | |||
|
842 | landing_rev=repo.landing_rev, | |||
|
843 | owner=repo.user.username, | |||
|
844 | fork_of=repo.fork.repo_name if repo.fork else None | |||
|
845 | ) | |||
|
846 | ||||
|
847 | return data | |||
|
848 | ||||
|
849 | @classmethod | |||
|
850 | def lock(cls, repo, user_id): | |||
|
851 | repo.locked = [user_id, time.time()] | |||
|
852 | Session().add(repo) | |||
|
853 | Session().commit() | |||
|
854 | ||||
|
855 | @classmethod | |||
|
856 | def unlock(cls, repo): | |||
|
857 | repo.locked = None | |||
|
858 | Session().add(repo) | |||
|
859 | Session().commit() | |||
|
860 | ||||
|
861 | @property | |||
|
862 | def last_db_change(self): | |||
|
863 | return self.updated_on | |||
|
864 | ||||
|
865 | #========================================================================== | |||
|
866 | # SCM PROPERTIES | |||
|
867 | #========================================================================== | |||
|
868 | ||||
|
869 | def get_changeset(self, rev=None): | |||
|
870 | return get_changeset_safe(self.scm_instance, rev) | |||
|
871 | ||||
|
872 | def get_landing_changeset(self): | |||
|
873 | """ | |||
|
874 | Returns landing changeset, or if that doesn't exist returns the tip | |||
|
875 | """ | |||
|
876 | cs = self.get_changeset(self.landing_rev) or self.get_changeset() | |||
|
877 | return cs | |||
|
878 | ||||
|
879 | def update_last_change(self, last_change=None): | |||
|
880 | if last_change is None: | |||
|
881 | last_change = datetime.datetime.now() | |||
|
882 | if self.updated_on is None or self.updated_on != last_change: | |||
|
883 | log.debug('updated repo %s with new date %s' % (self, last_change)) | |||
|
884 | self.updated_on = last_change | |||
|
885 | Session().add(self) | |||
|
886 | Session().commit() | |||
|
887 | ||||
|
888 | @property | |||
|
889 | def tip(self): | |||
|
890 | return self.get_changeset('tip') | |||
|
891 | ||||
|
892 | @property | |||
|
893 | def author(self): | |||
|
894 | return self.tip.author | |||
|
895 | ||||
|
896 | @property | |||
|
897 | def last_change(self): | |||
|
898 | return self.scm_instance.last_change | |||
|
899 | ||||
|
900 | def get_comments(self, revisions=None): | |||
|
901 | """ | |||
|
902 | Returns comments for this repository grouped by revisions | |||
|
903 | ||||
|
904 | :param revisions: filter query by revisions only | |||
|
905 | """ | |||
|
906 | cmts = ChangesetComment.query()\ | |||
|
907 | .filter(ChangesetComment.repo == self) | |||
|
908 | if revisions: | |||
|
909 | cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) | |||
|
910 | grouped = defaultdict(list) | |||
|
911 | for cmt in cmts.all(): | |||
|
912 | grouped[cmt.revision].append(cmt) | |||
|
913 | return grouped | |||
|
914 | ||||
|
915 | def statuses(self, revisions=None): | |||
|
916 | """ | |||
|
917 | Returns statuses for this repository | |||
|
918 | ||||
|
919 | :param revisions: list of revisions to get statuses for | |||
|
920 | :type revisions: list | |||
|
921 | """ | |||
|
922 | ||||
|
923 | statuses = ChangesetStatus.query()\ | |||
|
924 | .filter(ChangesetStatus.repo == self)\ | |||
|
925 | .filter(ChangesetStatus.version == 0) | |||
|
926 | if revisions: | |||
|
927 | statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) | |||
|
928 | grouped = {} | |||
27 |
|
929 | |||
28 | from rhodecode.model.db import * |
|
930 | #maybe we have open new pullrequest without a status ? | |
|
931 | stat = ChangesetStatus.STATUS_UNDER_REVIEW | |||
|
932 | status_lbl = ChangesetStatus.get_status_lbl(stat) | |||
|
933 | for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): | |||
|
934 | for rev in pr.revisions: | |||
|
935 | pr_id = pr.pull_request_id | |||
|
936 | pr_repo = pr.other_repo.repo_name | |||
|
937 | grouped[rev] = [stat, status_lbl, pr_id, pr_repo] | |||
|
938 | ||||
|
939 | for stat in statuses.all(): | |||
|
940 | pr_id = pr_repo = None | |||
|
941 | if stat.pull_request: | |||
|
942 | pr_id = stat.pull_request.pull_request_id | |||
|
943 | pr_repo = stat.pull_request.other_repo.repo_name | |||
|
944 | grouped[stat.revision] = [str(stat.status), stat.status_lbl, | |||
|
945 | pr_id, pr_repo] | |||
|
946 | return grouped | |||
|
947 | ||||
|
948 | #========================================================================== | |||
|
949 | # SCM CACHE INSTANCE | |||
|
950 | #========================================================================== | |||
|
951 | ||||
|
952 | @property | |||
|
953 | def invalidate(self): | |||
|
954 | return CacheInvalidation.invalidate(self.repo_name) | |||
|
955 | ||||
|
956 | def set_invalidate(self): | |||
|
957 | """ | |||
|
958 | set a cache for invalidation for this instance | |||
|
959 | """ | |||
|
960 | CacheInvalidation.set_invalidate(repo_name=self.repo_name) | |||
|
961 | ||||
|
962 | @LazyProperty | |||
|
963 | def scm_instance(self): | |||
|
964 | import rhodecode | |||
|
965 | full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) | |||
|
966 | if full_cache: | |||
|
967 | return self.scm_instance_cached() | |||
|
968 | return self.__get_instance() | |||
|
969 | ||||
|
970 | def scm_instance_cached(self, cache_map=None): | |||
|
971 | @cache_region('long_term') | |||
|
972 | def _c(repo_name): | |||
|
973 | return self.__get_instance() | |||
|
974 | rn = self.repo_name | |||
|
975 | log.debug('Getting cached instance of repo') | |||
|
976 | ||||
|
977 | if cache_map: | |||
|
978 | # get using prefilled cache_map | |||
|
979 | invalidate_repo = cache_map[self.repo_name] | |||
|
980 | if invalidate_repo: | |||
|
981 | invalidate_repo = (None if invalidate_repo.cache_active | |||
|
982 | else invalidate_repo) | |||
|
983 | else: | |||
|
984 | # get from invalidate | |||
|
985 | invalidate_repo = self.invalidate | |||
|
986 | ||||
|
987 | if invalidate_repo is not None: | |||
|
988 | region_invalidate(_c, None, rn) | |||
|
989 | # update our cache | |||
|
990 | CacheInvalidation.set_valid(invalidate_repo.cache_key) | |||
|
991 | return _c(rn) | |||
|
992 | ||||
|
993 | def __get_instance(self): | |||
|
994 | repo_full_path = self.repo_full_path | |||
|
995 | try: | |||
|
996 | alias = get_scm(repo_full_path)[0] | |||
|
997 | log.debug('Creating instance of %s repository' % alias) | |||
|
998 | backend = get_backend(alias) | |||
|
999 | except VCSError: | |||
|
1000 | log.error(traceback.format_exc()) | |||
|
1001 | log.error('Perhaps this repository is in db and not in ' | |||
|
1002 | 'filesystem run rescan repositories with ' | |||
|
1003 | '"destroy old data " option from admin panel') | |||
|
1004 | return | |||
|
1005 | ||||
|
1006 | if alias == 'hg': | |||
|
1007 | ||||
|
1008 | repo = backend(safe_str(repo_full_path), create=False, | |||
|
1009 | baseui=self._ui) | |||
|
1010 | # skip hidden web repository | |||
|
1011 | if repo._get_hidden(): | |||
|
1012 | return | |||
|
1013 | else: | |||
|
1014 | repo = backend(repo_full_path, create=False) | |||
|
1015 | ||||
|
1016 | return repo | |||
|
1017 | ||||
|
1018 | ||||
|
1019 | class RepoGroup(Base, BaseModel): | |||
|
1020 | __tablename__ = 'groups' | |||
|
1021 | __table_args__ = ( | |||
|
1022 | UniqueConstraint('group_name', 'group_parent_id'), | |||
|
1023 | CheckConstraint('group_id != group_parent_id'), | |||
|
1024 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1025 | 'mysql_charset': 'utf8'}, | |||
|
1026 | ) | |||
|
1027 | __mapper_args__ = {'order_by': 'group_name'} | |||
|
1028 | ||||
|
1029 | group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1030 | group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |||
|
1031 | group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) | |||
|
1032 | group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
1033 | enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) | |||
|
1034 | ||||
|
1035 | repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') | |||
|
1036 | users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all') | |||
|
1037 | ||||
|
1038 | parent_group = relationship('RepoGroup', remote_side=group_id) | |||
|
1039 | ||||
|
1040 | def __init__(self, group_name='', parent_group=None): | |||
|
1041 | self.group_name = group_name | |||
|
1042 | self.parent_group = parent_group | |||
|
1043 | ||||
|
1044 | def __unicode__(self): | |||
|
1045 | return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, | |||
|
1046 | self.group_name) | |||
|
1047 | ||||
|
1048 | @classmethod | |||
|
1049 | def groups_choices(cls, check_perms=False): | |||
|
1050 | from webhelpers.html import literal as _literal | |||
|
1051 | from rhodecode.model.scm import ScmModel | |||
|
1052 | groups = cls.query().all() | |||
|
1053 | if check_perms: | |||
|
1054 | #filter group user have access to, it's done | |||
|
1055 | #magically inside ScmModel based on current user | |||
|
1056 | groups = ScmModel().get_repos_groups(groups) | |||
|
1057 | repo_groups = [('', '')] | |||
|
1058 | sep = ' » ' | |||
|
1059 | _name = lambda k: _literal(sep.join(k)) | |||
|
1060 | ||||
|
1061 | repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) | |||
|
1062 | for x in groups]) | |||
|
1063 | ||||
|
1064 | repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) | |||
|
1065 | return repo_groups | |||
|
1066 | ||||
|
1067 | @classmethod | |||
|
1068 | def url_sep(cls): | |||
|
1069 | return URL_SEP | |||
|
1070 | ||||
|
1071 | @classmethod | |||
|
1072 | def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): | |||
|
1073 | if case_insensitive: | |||
|
1074 | gr = cls.query()\ | |||
|
1075 | .filter(cls.group_name.ilike(group_name)) | |||
|
1076 | else: | |||
|
1077 | gr = cls.query()\ | |||
|
1078 | .filter(cls.group_name == group_name) | |||
|
1079 | if cache: | |||
|
1080 | gr = gr.options(FromCache( | |||
|
1081 | "sql_cache_short", | |||
|
1082 | "get_group_%s" % _hash_key(group_name) | |||
|
1083 | ) | |||
|
1084 | ) | |||
|
1085 | return gr.scalar() | |||
|
1086 | ||||
|
1087 | @property | |||
|
1088 | def parents(self): | |||
|
1089 | parents_recursion_limit = 5 | |||
|
1090 | groups = [] | |||
|
1091 | if self.parent_group is None: | |||
|
1092 | return groups | |||
|
1093 | cur_gr = self.parent_group | |||
|
1094 | groups.insert(0, cur_gr) | |||
|
1095 | cnt = 0 | |||
|
1096 | while 1: | |||
|
1097 | cnt += 1 | |||
|
1098 | gr = getattr(cur_gr, 'parent_group', None) | |||
|
1099 | cur_gr = cur_gr.parent_group | |||
|
1100 | if gr is None: | |||
|
1101 | break | |||
|
1102 | if cnt == parents_recursion_limit: | |||
|
1103 | # this will prevent accidental infinit loops | |||
|
1104 | log.error('group nested more than %s' % | |||
|
1105 | parents_recursion_limit) | |||
|
1106 | break | |||
|
1107 | ||||
|
1108 | groups.insert(0, gr) | |||
|
1109 | return groups | |||
|
1110 | ||||
|
1111 | @property | |||
|
1112 | def children(self): | |||
|
1113 | return RepoGroup.query().filter(RepoGroup.parent_group == self) | |||
|
1114 | ||||
|
1115 | @property | |||
|
1116 | def name(self): | |||
|
1117 | return self.group_name.split(RepoGroup.url_sep())[-1] | |||
|
1118 | ||||
|
1119 | @property | |||
|
1120 | def full_path(self): | |||
|
1121 | return self.group_name | |||
|
1122 | ||||
|
1123 | @property | |||
|
1124 | def full_path_splitted(self): | |||
|
1125 | return self.group_name.split(RepoGroup.url_sep()) | |||
|
1126 | ||||
|
1127 | @property | |||
|
1128 | def repositories(self): | |||
|
1129 | return Repository.query()\ | |||
|
1130 | .filter(Repository.group == self)\ | |||
|
1131 | .order_by(Repository.repo_name) | |||
|
1132 | ||||
|
1133 | @property | |||
|
1134 | def repositories_recursive_count(self): | |||
|
1135 | cnt = self.repositories.count() | |||
|
1136 | ||||
|
1137 | def children_count(group): | |||
|
1138 | cnt = 0 | |||
|
1139 | for child in group.children: | |||
|
1140 | cnt += child.repositories.count() | |||
|
1141 | cnt += children_count(child) | |||
|
1142 | return cnt | |||
|
1143 | ||||
|
1144 | return cnt + children_count(self) | |||
|
1145 | ||||
|
1146 | def recursive_groups_and_repos(self): | |||
|
1147 | """ | |||
|
1148 | Recursive return all groups, with repositories in those groups | |||
|
1149 | """ | |||
|
1150 | all_ = [] | |||
|
1151 | ||||
|
1152 | def _get_members(root_gr): | |||
|
1153 | for r in root_gr.repositories: | |||
|
1154 | all_.append(r) | |||
|
1155 | childs = root_gr.children.all() | |||
|
1156 | if childs: | |||
|
1157 | for gr in childs: | |||
|
1158 | all_.append(gr) | |||
|
1159 | _get_members(gr) | |||
|
1160 | ||||
|
1161 | _get_members(self) | |||
|
1162 | return [self] + all_ | |||
|
1163 | ||||
|
1164 | def get_new_name(self, group_name): | |||
|
1165 | """ | |||
|
1166 | returns new full group name based on parent and new name | |||
|
1167 | ||||
|
1168 | :param group_name: | |||
|
1169 | """ | |||
|
1170 | path_prefix = (self.parent_group.full_path_splitted if | |||
|
1171 | self.parent_group else []) | |||
|
1172 | return RepoGroup.url_sep().join(path_prefix + [group_name]) | |||
|
1173 | ||||
|
1174 | ||||
|
1175 | class Permission(Base, BaseModel): | |||
|
1176 | __tablename__ = 'permissions' | |||
|
1177 | __table_args__ = ( | |||
|
1178 | Index('p_perm_name_idx', 'permission_name'), | |||
|
1179 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1180 | 'mysql_charset': 'utf8'}, | |||
|
1181 | ) | |||
|
1182 | PERMS = [ | |||
|
1183 | ('repository.none', _('Repository no access')), | |||
|
1184 | ('repository.read', _('Repository read access')), | |||
|
1185 | ('repository.write', _('Repository write access')), | |||
|
1186 | ('repository.admin', _('Repository admin access')), | |||
|
1187 | ||||
|
1188 | ('group.none', _('Repositories Group no access')), | |||
|
1189 | ('group.read', _('Repositories Group read access')), | |||
|
1190 | ('group.write', _('Repositories Group write access')), | |||
|
1191 | ('group.admin', _('Repositories Group admin access')), | |||
|
1192 | ||||
|
1193 | ('hg.admin', _('RhodeCode Administrator')), | |||
|
1194 | ('hg.create.none', _('Repository creation disabled')), | |||
|
1195 | ('hg.create.repository', _('Repository creation enabled')), | |||
|
1196 | ('hg.fork.none', _('Repository forking disabled')), | |||
|
1197 | ('hg.fork.repository', _('Repository forking enabled')), | |||
|
1198 | ('hg.register.none', _('Register disabled')), | |||
|
1199 | ('hg.register.manual_activate', _('Register new user with RhodeCode ' | |||
|
1200 | 'with manual activation')), | |||
|
1201 | ||||
|
1202 | ('hg.register.auto_activate', _('Register new user with RhodeCode ' | |||
|
1203 | 'with auto activation')), | |||
|
1204 | ] | |||
|
1205 | ||||
|
1206 | # defines which permissions are more important higher the more important | |||
|
1207 | PERM_WEIGHTS = { | |||
|
1208 | 'repository.none': 0, | |||
|
1209 | 'repository.read': 1, | |||
|
1210 | 'repository.write': 3, | |||
|
1211 | 'repository.admin': 4, | |||
|
1212 | ||||
|
1213 | 'group.none': 0, | |||
|
1214 | 'group.read': 1, | |||
|
1215 | 'group.write': 3, | |||
|
1216 | 'group.admin': 4, | |||
|
1217 | ||||
|
1218 | 'hg.fork.none': 0, | |||
|
1219 | 'hg.fork.repository': 1, | |||
|
1220 | 'hg.create.none': 0, | |||
|
1221 | 'hg.create.repository':1 | |||
|
1222 | } | |||
|
1223 | ||||
|
1224 | permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1225 | permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
1226 | permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
1227 | ||||
|
1228 | def __unicode__(self): | |||
|
1229 | return u"<%s('%s:%s')>" % ( | |||
|
1230 | self.__class__.__name__, self.permission_id, self.permission_name | |||
|
1231 | ) | |||
|
1232 | ||||
|
1233 | @classmethod | |||
|
1234 | def get_by_key(cls, key): | |||
|
1235 | return cls.query().filter(cls.permission_name == key).scalar() | |||
|
1236 | ||||
|
1237 | @classmethod | |||
|
1238 | def get_default_perms(cls, default_user_id): | |||
|
1239 | q = Session().query(UserRepoToPerm, Repository, cls)\ | |||
|
1240 | .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ | |||
|
1241 | .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ | |||
|
1242 | .filter(UserRepoToPerm.user_id == default_user_id) | |||
|
1243 | ||||
|
1244 | return q.all() | |||
|
1245 | ||||
|
1246 | @classmethod | |||
|
1247 | def get_default_group_perms(cls, default_user_id): | |||
|
1248 | q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ | |||
|
1249 | .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ | |||
|
1250 | .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ | |||
|
1251 | .filter(UserRepoGroupToPerm.user_id == default_user_id) | |||
|
1252 | ||||
|
1253 | return q.all() | |||
|
1254 | ||||
|
1255 | ||||
|
1256 | class UserRepoToPerm(Base, BaseModel): | |||
|
1257 | __tablename__ = 'repo_to_perm' | |||
|
1258 | __table_args__ = ( | |||
|
1259 | UniqueConstraint('user_id', 'repository_id', 'permission_id'), | |||
|
1260 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1261 | 'mysql_charset': 'utf8'} | |||
|
1262 | ) | |||
|
1263 | repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1264 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |||
|
1265 | permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |||
|
1266 | repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) | |||
|
1267 | ||||
|
1268 | user = relationship('User') | |||
|
1269 | repository = relationship('Repository') | |||
|
1270 | permission = relationship('Permission') | |||
|
1271 | ||||
|
1272 | @classmethod | |||
|
1273 | def create(cls, user, repository, permission): | |||
|
1274 | n = cls() | |||
|
1275 | n.user = user | |||
|
1276 | n.repository = repository | |||
|
1277 | n.permission = permission | |||
|
1278 | Session().add(n) | |||
|
1279 | return n | |||
|
1280 | ||||
|
1281 | def __unicode__(self): | |||
|
1282 | return u'<user:%s => %s >' % (self.user, self.repository) | |||
|
1283 | ||||
|
1284 | ||||
|
1285 | class UserToPerm(Base, BaseModel): | |||
|
1286 | __tablename__ = 'user_to_perm' | |||
|
1287 | __table_args__ = ( | |||
|
1288 | UniqueConstraint('user_id', 'permission_id'), | |||
|
1289 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1290 | 'mysql_charset': 'utf8'} | |||
|
1291 | ) | |||
|
1292 | user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1293 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |||
|
1294 | permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |||
|
1295 | ||||
|
1296 | user = relationship('User') | |||
|
1297 | permission = relationship('Permission', lazy='joined') | |||
|
1298 | ||||
|
1299 | ||||
|
1300 | class UsersGroupRepoToPerm(Base, BaseModel): | |||
|
1301 | __tablename__ = 'users_group_repo_to_perm' | |||
|
1302 | __table_args__ = ( | |||
|
1303 | UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), | |||
|
1304 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1305 | 'mysql_charset': 'utf8'} | |||
|
1306 | ) | |||
|
1307 | users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1308 | users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |||
|
1309 | permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |||
|
1310 | repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) | |||
|
1311 | ||||
|
1312 | users_group = relationship('UsersGroup') | |||
|
1313 | permission = relationship('Permission') | |||
|
1314 | repository = relationship('Repository') | |||
|
1315 | ||||
|
1316 | @classmethod | |||
|
1317 | def create(cls, users_group, repository, permission): | |||
|
1318 | n = cls() | |||
|
1319 | n.users_group = users_group | |||
|
1320 | n.repository = repository | |||
|
1321 | n.permission = permission | |||
|
1322 | Session().add(n) | |||
|
1323 | return n | |||
|
1324 | ||||
|
1325 | def __unicode__(self): | |||
|
1326 | return u'<userGroup:%s => %s >' % (self.users_group, self.repository) | |||
|
1327 | ||||
|
1328 | ||||
|
1329 | class UsersGroupToPerm(Base, BaseModel): | |||
|
1330 | __tablename__ = 'users_group_to_perm' | |||
|
1331 | __table_args__ = ( | |||
|
1332 | UniqueConstraint('users_group_id', 'permission_id',), | |||
|
1333 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1334 | 'mysql_charset': 'utf8'} | |||
|
1335 | ) | |||
|
1336 | users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1337 | users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |||
|
1338 | permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |||
|
1339 | ||||
|
1340 | users_group = relationship('UsersGroup') | |||
|
1341 | permission = relationship('Permission') | |||
|
1342 | ||||
|
1343 | ||||
|
1344 | class UserRepoGroupToPerm(Base, BaseModel): | |||
|
1345 | __tablename__ = 'user_repo_group_to_perm' | |||
|
1346 | __table_args__ = ( | |||
|
1347 | UniqueConstraint('user_id', 'group_id', 'permission_id'), | |||
|
1348 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1349 | 'mysql_charset': 'utf8'} | |||
|
1350 | ) | |||
|
1351 | ||||
|
1352 | group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1353 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |||
|
1354 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) | |||
|
1355 | permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |||
|
1356 | ||||
|
1357 | user = relationship('User') | |||
|
1358 | group = relationship('RepoGroup') | |||
|
1359 | permission = relationship('Permission') | |||
|
1360 | ||||
|
1361 | ||||
|
1362 | class UsersGroupRepoGroupToPerm(Base, BaseModel): | |||
|
1363 | __tablename__ = 'users_group_repo_group_to_perm' | |||
|
1364 | __table_args__ = ( | |||
|
1365 | UniqueConstraint('users_group_id', 'group_id'), | |||
|
1366 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1367 | 'mysql_charset': 'utf8'} | |||
|
1368 | ) | |||
|
1369 | ||||
|
1370 | 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) | |||
|
1371 | users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |||
|
1372 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) | |||
|
1373 | permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |||
|
1374 | ||||
|
1375 | users_group = relationship('UsersGroup') | |||
|
1376 | permission = relationship('Permission') | |||
|
1377 | group = relationship('RepoGroup') | |||
|
1378 | ||||
|
1379 | ||||
|
1380 | class Statistics(Base, BaseModel): | |||
|
1381 | __tablename__ = 'statistics' | |||
|
1382 | __table_args__ = ( | |||
|
1383 | UniqueConstraint('repository_id'), | |||
|
1384 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1385 | 'mysql_charset': 'utf8'} | |||
|
1386 | ) | |||
|
1387 | stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1388 | repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) | |||
|
1389 | stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) | |||
|
1390 | commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data | |||
|
1391 | commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data | |||
|
1392 | languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data | |||
|
1393 | ||||
|
1394 | repository = relationship('Repository', single_parent=True) | |||
|
1395 | ||||
|
1396 | ||||
|
1397 | class UserFollowing(Base, BaseModel): | |||
|
1398 | __tablename__ = 'user_followings' | |||
|
1399 | __table_args__ = ( | |||
|
1400 | UniqueConstraint('user_id', 'follows_repository_id'), | |||
|
1401 | UniqueConstraint('user_id', 'follows_user_id'), | |||
|
1402 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1403 | 'mysql_charset': 'utf8'} | |||
|
1404 | ) | |||
|
1405 | ||||
|
1406 | user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1407 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |||
|
1408 | follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) | |||
|
1409 | follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |||
|
1410 | follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |||
|
1411 | ||||
|
1412 | user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') | |||
|
1413 | ||||
|
1414 | follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') | |||
|
1415 | follows_repository = relationship('Repository', order_by='Repository.repo_name') | |||
|
1416 | ||||
|
1417 | @classmethod | |||
|
1418 | def get_repo_followers(cls, repo_id): | |||
|
1419 | return cls.query().filter(cls.follows_repo_id == repo_id) | |||
|
1420 | ||||
|
1421 | ||||
|
1422 | class CacheInvalidation(Base, BaseModel): | |||
|
1423 | __tablename__ = 'cache_invalidation' | |||
|
1424 | __table_args__ = ( | |||
|
1425 | UniqueConstraint('cache_key'), | |||
|
1426 | Index('key_idx', 'cache_key'), | |||
|
1427 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1428 | 'mysql_charset': 'utf8'}, | |||
|
1429 | ) | |||
|
1430 | cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
1431 | cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
1432 | cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |||
|
1433 | cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) | |||
|
1434 | ||||
|
1435 | def __init__(self, cache_key, cache_args=''): | |||
|
1436 | self.cache_key = cache_key | |||
|
1437 | self.cache_args = cache_args | |||
|
1438 | self.cache_active = False | |||
|
1439 | ||||
|
1440 | def __unicode__(self): | |||
|
1441 | return u"<%s('%s:%s')>" % (self.__class__.__name__, | |||
|
1442 | self.cache_id, self.cache_key) | |||
|
1443 | ||||
|
1444 | @property | |||
|
1445 | def prefix(self): | |||
|
1446 | _split = self.cache_key.split(self.cache_args, 1) | |||
|
1447 | if _split and len(_split) == 2: | |||
|
1448 | return _split[0] | |||
|
1449 | return '' | |||
|
1450 | ||||
|
1451 | @classmethod | |||
|
1452 | def clear_cache(cls): | |||
|
1453 | cls.query().delete() | |||
|
1454 | ||||
|
1455 | @classmethod | |||
|
1456 | def _get_key(cls, key): | |||
|
1457 | """ | |||
|
1458 | Wrapper for generating a key, together with a prefix | |||
|
1459 | ||||
|
1460 | :param key: | |||
|
1461 | """ | |||
|
1462 | import rhodecode | |||
|
1463 | prefix = '' | |||
|
1464 | org_key = key | |||
|
1465 | iid = rhodecode.CONFIG.get('instance_id') | |||
|
1466 | if iid: | |||
|
1467 | prefix = iid | |||
|
1468 | ||||
|
1469 | return "%s%s" % (prefix, key), prefix, org_key | |||
|
1470 | ||||
|
1471 | @classmethod | |||
|
1472 | def get_by_key(cls, key): | |||
|
1473 | return cls.query().filter(cls.cache_key == key).scalar() | |||
|
1474 | ||||
|
1475 | @classmethod | |||
|
1476 | def get_by_repo_name(cls, repo_name): | |||
|
1477 | return cls.query().filter(cls.cache_args == repo_name).all() | |||
|
1478 | ||||
|
1479 | @classmethod | |||
|
1480 | def _get_or_create_key(cls, key, repo_name, commit=True): | |||
|
1481 | inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() | |||
|
1482 | if not inv_obj: | |||
|
1483 | try: | |||
|
1484 | inv_obj = CacheInvalidation(key, repo_name) | |||
|
1485 | Session().add(inv_obj) | |||
|
1486 | if commit: | |||
|
1487 | Session().commit() | |||
|
1488 | except Exception: | |||
|
1489 | log.error(traceback.format_exc()) | |||
|
1490 | Session().rollback() | |||
|
1491 | return inv_obj | |||
|
1492 | ||||
|
1493 | @classmethod | |||
|
1494 | def invalidate(cls, key): | |||
|
1495 | """ | |||
|
1496 | Returns Invalidation object if this given key should be invalidated | |||
|
1497 | None otherwise. `cache_active = False` means that this cache | |||
|
1498 | state is not valid and needs to be invalidated | |||
|
1499 | ||||
|
1500 | :param key: | |||
|
1501 | """ | |||
|
1502 | repo_name = key | |||
|
1503 | repo_name = remove_suffix(repo_name, '_README') | |||
|
1504 | repo_name = remove_suffix(repo_name, '_RSS') | |||
|
1505 | repo_name = remove_suffix(repo_name, '_ATOM') | |||
|
1506 | ||||
|
1507 | # adds instance prefix | |||
|
1508 | key, _prefix, _org_key = cls._get_key(key) | |||
|
1509 | inv = cls._get_or_create_key(key, repo_name) | |||
|
1510 | ||||
|
1511 | if inv and inv.cache_active is False: | |||
|
1512 | return inv | |||
|
1513 | ||||
|
1514 | @classmethod | |||
|
1515 | def set_invalidate(cls, key=None, repo_name=None): | |||
|
1516 | """ | |||
|
1517 | Mark this Cache key for invalidation, either by key or whole | |||
|
1518 | cache sets based on repo_name | |||
|
1519 | ||||
|
1520 | :param key: | |||
|
1521 | """ | |||
|
1522 | if key: | |||
|
1523 | key, _prefix, _org_key = cls._get_key(key) | |||
|
1524 | inv_objs = Session().query(cls).filter(cls.cache_key == key).all() | |||
|
1525 | elif repo_name: | |||
|
1526 | inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() | |||
|
1527 | ||||
|
1528 | log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s' | |||
|
1529 | % (len(inv_objs), key, repo_name)) | |||
|
1530 | try: | |||
|
1531 | for inv_obj in inv_objs: | |||
|
1532 | inv_obj.cache_active = False | |||
|
1533 | Session().add(inv_obj) | |||
|
1534 | Session().commit() | |||
|
1535 | except Exception: | |||
|
1536 | log.error(traceback.format_exc()) | |||
|
1537 | Session().rollback() | |||
|
1538 | ||||
|
1539 | @classmethod | |||
|
1540 | def set_valid(cls, key): | |||
|
1541 | """ | |||
|
1542 | Mark this cache key as active and currently cached | |||
|
1543 | ||||
|
1544 | :param key: | |||
|
1545 | """ | |||
|
1546 | inv_obj = cls.get_by_key(key) | |||
|
1547 | inv_obj.cache_active = True | |||
|
1548 | Session().add(inv_obj) | |||
|
1549 | Session().commit() | |||
|
1550 | ||||
|
1551 | @classmethod | |||
|
1552 | def get_cache_map(cls): | |||
|
1553 | ||||
|
1554 | class cachemapdict(dict): | |||
|
1555 | ||||
|
1556 | def __init__(self, *args, **kwargs): | |||
|
1557 | fixkey = kwargs.get('fixkey') | |||
|
1558 | if fixkey: | |||
|
1559 | del kwargs['fixkey'] | |||
|
1560 | self.fixkey = fixkey | |||
|
1561 | super(cachemapdict, self).__init__(*args, **kwargs) | |||
|
1562 | ||||
|
1563 | def __getattr__(self, name): | |||
|
1564 | key = name | |||
|
1565 | if self.fixkey: | |||
|
1566 | key, _prefix, _org_key = cls._get_key(key) | |||
|
1567 | if key in self.__dict__: | |||
|
1568 | return self.__dict__[key] | |||
|
1569 | else: | |||
|
1570 | return self[key] | |||
|
1571 | ||||
|
1572 | def __getitem__(self, key): | |||
|
1573 | if self.fixkey: | |||
|
1574 | key, _prefix, _org_key = cls._get_key(key) | |||
|
1575 | try: | |||
|
1576 | return super(cachemapdict, self).__getitem__(key) | |||
|
1577 | except KeyError: | |||
|
1578 | return | |||
|
1579 | ||||
|
1580 | cache_map = cachemapdict(fixkey=True) | |||
|
1581 | for obj in cls.query().all(): | |||
|
1582 | cache_map[obj.cache_key] = cachemapdict(obj.get_dict()) | |||
|
1583 | return cache_map | |||
|
1584 | ||||
|
1585 | ||||
|
1586 | class ChangesetComment(Base, BaseModel): | |||
|
1587 | __tablename__ = 'changeset_comments' | |||
|
1588 | __table_args__ = ( | |||
|
1589 | Index('cc_revision_idx', 'revision'), | |||
|
1590 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1591 | 'mysql_charset': 'utf8'}, | |||
|
1592 | ) | |||
|
1593 | comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) | |||
|
1594 | repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |||
|
1595 | revision = Column('revision', String(40), nullable=True) | |||
|
1596 | pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) | |||
|
1597 | line_no = Column('line_no', Unicode(10), nullable=True) | |||
|
1598 | hl_lines = Column('hl_lines', Unicode(512), nullable=True) | |||
|
1599 | f_path = Column('f_path', Unicode(1000), nullable=True) | |||
|
1600 | user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) | |||
|
1601 | text = Column('text', UnicodeText(25000), nullable=False) | |||
|
1602 | created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |||
|
1603 | modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |||
|
1604 | ||||
|
1605 | author = relationship('User', lazy='joined') | |||
|
1606 | repo = relationship('Repository') | |||
|
1607 | status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") | |||
|
1608 | pull_request = relationship('PullRequest', lazy='joined') | |||
|
1609 | ||||
|
1610 | @classmethod | |||
|
1611 | def get_users(cls, revision=None, pull_request_id=None): | |||
|
1612 | """ | |||
|
1613 | Returns user associated with this ChangesetComment. ie those | |||
|
1614 | who actually commented | |||
|
1615 | ||||
|
1616 | :param cls: | |||
|
1617 | :param revision: | |||
|
1618 | """ | |||
|
1619 | q = Session().query(User)\ | |||
|
1620 | .join(ChangesetComment.author) | |||
|
1621 | if revision: | |||
|
1622 | q = q.filter(cls.revision == revision) | |||
|
1623 | elif pull_request_id: | |||
|
1624 | q = q.filter(cls.pull_request_id == pull_request_id) | |||
|
1625 | return q.all() | |||
|
1626 | ||||
|
1627 | ||||
|
1628 | class ChangesetStatus(Base, BaseModel): | |||
|
1629 | __tablename__ = 'changeset_statuses' | |||
|
1630 | __table_args__ = ( | |||
|
1631 | Index('cs_revision_idx', 'revision'), | |||
|
1632 | Index('cs_version_idx', 'version'), | |||
|
1633 | UniqueConstraint('repo_id', 'revision', 'version'), | |||
|
1634 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1635 | 'mysql_charset': 'utf8'} | |||
|
1636 | ) | |||
|
1637 | STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' | |||
|
1638 | STATUS_APPROVED = 'approved' | |||
|
1639 | STATUS_REJECTED = 'rejected' | |||
|
1640 | STATUS_UNDER_REVIEW = 'under_review' | |||
|
1641 | ||||
|
1642 | STATUSES = [ | |||
|
1643 | (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default | |||
|
1644 | (STATUS_APPROVED, _("Approved")), | |||
|
1645 | (STATUS_REJECTED, _("Rejected")), | |||
|
1646 | (STATUS_UNDER_REVIEW, _("Under Review")), | |||
|
1647 | ] | |||
|
1648 | ||||
|
1649 | changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) | |||
|
1650 | repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |||
|
1651 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) | |||
|
1652 | revision = Column('revision', String(40), nullable=False) | |||
|
1653 | status = Column('status', String(128), nullable=False, default=DEFAULT) | |||
|
1654 | changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) | |||
|
1655 | modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) | |||
|
1656 | version = Column('version', Integer(), nullable=False, default=0) | |||
|
1657 | pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) | |||
|
1658 | ||||
|
1659 | author = relationship('User', lazy='joined') | |||
|
1660 | repo = relationship('Repository') | |||
|
1661 | comment = relationship('ChangesetComment', lazy='joined') | |||
|
1662 | pull_request = relationship('PullRequest', lazy='joined') | |||
|
1663 | ||||
|
1664 | def __unicode__(self): | |||
|
1665 | return u"<%s('%s:%s')>" % ( | |||
|
1666 | self.__class__.__name__, | |||
|
1667 | self.status, self.author | |||
|
1668 | ) | |||
|
1669 | ||||
|
1670 | @classmethod | |||
|
1671 | def get_status_lbl(cls, value): | |||
|
1672 | return dict(cls.STATUSES).get(value) | |||
|
1673 | ||||
|
1674 | @property | |||
|
1675 | def status_lbl(self): | |||
|
1676 | return ChangesetStatus.get_status_lbl(self.status) | |||
|
1677 | ||||
|
1678 | ||||
|
1679 | class PullRequest(Base, BaseModel): | |||
|
1680 | __tablename__ = 'pull_requests' | |||
|
1681 | __table_args__ = ( | |||
|
1682 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1683 | 'mysql_charset': 'utf8'}, | |||
|
1684 | ) | |||
|
1685 | ||||
|
1686 | STATUS_NEW = u'new' | |||
|
1687 | STATUS_OPEN = u'open' | |||
|
1688 | STATUS_CLOSED = u'closed' | |||
|
1689 | ||||
|
1690 | pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) | |||
|
1691 | title = Column('title', Unicode(256), nullable=True) | |||
|
1692 | description = Column('description', UnicodeText(10240), nullable=True) | |||
|
1693 | status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) | |||
|
1694 | created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |||
|
1695 | updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |||
|
1696 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) | |||
|
1697 | _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max | |||
|
1698 | org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |||
|
1699 | org_ref = Column('org_ref', Unicode(256), nullable=False) | |||
|
1700 | other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |||
|
1701 | other_ref = Column('other_ref', Unicode(256), nullable=False) | |||
|
1702 | ||||
|
1703 | @hybrid_property | |||
|
1704 | def revisions(self): | |||
|
1705 | return self._revisions.split(':') | |||
|
1706 | ||||
|
1707 | @revisions.setter | |||
|
1708 | def revisions(self, val): | |||
|
1709 | self._revisions = ':'.join(val) | |||
|
1710 | ||||
|
1711 | author = relationship('User', lazy='joined') | |||
|
1712 | reviewers = relationship('PullRequestReviewers', | |||
|
1713 | cascade="all, delete, delete-orphan") | |||
|
1714 | org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') | |||
|
1715 | other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') | |||
|
1716 | statuses = relationship('ChangesetStatus') | |||
|
1717 | comments = relationship('ChangesetComment', | |||
|
1718 | cascade="all, delete, delete-orphan") | |||
|
1719 | ||||
|
1720 | def is_closed(self): | |||
|
1721 | return self.status == self.STATUS_CLOSED | |||
|
1722 | ||||
|
1723 | def __json__(self): | |||
|
1724 | return dict( | |||
|
1725 | revisions=self.revisions | |||
|
1726 | ) | |||
|
1727 | ||||
|
1728 | ||||
|
1729 | class PullRequestReviewers(Base, BaseModel): | |||
|
1730 | __tablename__ = 'pull_request_reviewers' | |||
|
1731 | __table_args__ = ( | |||
|
1732 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1733 | 'mysql_charset': 'utf8'}, | |||
|
1734 | ) | |||
|
1735 | ||||
|
1736 | def __init__(self, user=None, pull_request=None): | |||
|
1737 | self.user = user | |||
|
1738 | self.pull_request = pull_request | |||
|
1739 | ||||
|
1740 | pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) | |||
|
1741 | pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) | |||
|
1742 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) | |||
|
1743 | ||||
|
1744 | user = relationship('User') | |||
|
1745 | pull_request = relationship('PullRequest') | |||
|
1746 | ||||
|
1747 | ||||
|
1748 | class Notification(Base, BaseModel): | |||
|
1749 | __tablename__ = 'notifications' | |||
|
1750 | __table_args__ = ( | |||
|
1751 | Index('notification_type_idx', 'type'), | |||
|
1752 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1753 | 'mysql_charset': 'utf8'}, | |||
|
1754 | ) | |||
|
1755 | ||||
|
1756 | TYPE_CHANGESET_COMMENT = u'cs_comment' | |||
|
1757 | TYPE_MESSAGE = u'message' | |||
|
1758 | TYPE_MENTION = u'mention' | |||
|
1759 | TYPE_REGISTRATION = u'registration' | |||
|
1760 | TYPE_PULL_REQUEST = u'pull_request' | |||
|
1761 | TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' | |||
|
1762 | ||||
|
1763 | notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) | |||
|
1764 | subject = Column('subject', Unicode(512), nullable=True) | |||
|
1765 | body = Column('body', UnicodeText(50000), nullable=True) | |||
|
1766 | created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) | |||
|
1767 | created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |||
|
1768 | type_ = Column('type', Unicode(256)) | |||
|
1769 | ||||
|
1770 | created_by_user = relationship('User') | |||
|
1771 | notifications_to_users = relationship('UserNotification', lazy='joined', | |||
|
1772 | cascade="all, delete, delete-orphan") | |||
|
1773 | ||||
|
1774 | @property | |||
|
1775 | def recipients(self): | |||
|
1776 | return [x.user for x in UserNotification.query()\ | |||
|
1777 | .filter(UserNotification.notification == self)\ | |||
|
1778 | .order_by(UserNotification.user_id.asc()).all()] | |||
|
1779 | ||||
|
1780 | @classmethod | |||
|
1781 | def create(cls, created_by, subject, body, recipients, type_=None): | |||
|
1782 | if type_ is None: | |||
|
1783 | type_ = Notification.TYPE_MESSAGE | |||
|
1784 | ||||
|
1785 | notification = cls() | |||
|
1786 | notification.created_by_user = created_by | |||
|
1787 | notification.subject = subject | |||
|
1788 | notification.body = body | |||
|
1789 | notification.type_ = type_ | |||
|
1790 | notification.created_on = datetime.datetime.now() | |||
|
1791 | ||||
|
1792 | for u in recipients: | |||
|
1793 | assoc = UserNotification() | |||
|
1794 | assoc.notification = notification | |||
|
1795 | u.notifications.append(assoc) | |||
|
1796 | Session().add(notification) | |||
|
1797 | return notification | |||
|
1798 | ||||
|
1799 | @property | |||
|
1800 | def description(self): | |||
|
1801 | from rhodecode.model.notification import NotificationModel | |||
|
1802 | return NotificationModel().make_description(self) | |||
|
1803 | ||||
|
1804 | ||||
|
1805 | class UserNotification(Base, BaseModel): | |||
|
1806 | __tablename__ = 'user_to_notification' | |||
|
1807 | __table_args__ = ( | |||
|
1808 | UniqueConstraint('user_id', 'notification_id'), | |||
|
1809 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1810 | 'mysql_charset': 'utf8'} | |||
|
1811 | ) | |||
|
1812 | user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) | |||
|
1813 | notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) | |||
|
1814 | read = Column('read', Boolean, default=False) | |||
|
1815 | sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) | |||
|
1816 | ||||
|
1817 | user = relationship('User', lazy="joined") | |||
|
1818 | notification = relationship('Notification', lazy="joined", | |||
|
1819 | order_by=lambda: Notification.created_on.desc(),) | |||
|
1820 | ||||
|
1821 | def mark_as_read(self): | |||
|
1822 | self.read = True | |||
|
1823 | Session().add(self) | |||
|
1824 | ||||
|
1825 | ||||
|
1826 | class DbMigrateVersion(Base, BaseModel): | |||
|
1827 | __tablename__ = 'db_migrate_version' | |||
|
1828 | __table_args__ = ( | |||
|
1829 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
1830 | 'mysql_charset': 'utf8'}, | |||
|
1831 | ) | |||
|
1832 | repository_id = Column('repository_id', String(250), primary_key=True) | |||
|
1833 | repository_path = Column('repository_path', Text) | |||
|
1834 | version = Column('version', Integer) |
@@ -12,6 +12,7 b' from rhodecode.lib.dbmigrate.migrate.cha' | |||||
12 |
|
12 | |||
13 | from rhodecode.model.meta import Base |
|
13 | from rhodecode.model.meta import Base | |
14 | from rhodecode.model import meta |
|
14 | from rhodecode.model import meta | |
|
15 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
15 |
|
16 | |||
16 | log = logging.getLogger(__name__) |
|
17 | log = logging.getLogger(__name__) | |
17 |
|
18 | |||
@@ -49,12 +50,7 b' def upgrade(migrate_engine):' | |||||
49 | tbl = ChangesetStatus.__table__ |
|
50 | tbl = ChangesetStatus.__table__ | |
50 | tbl.create() |
|
51 | tbl.create() | |
51 |
|
52 | |||
52 | ## RESET COMPLETLY THE metadata for sqlalchemy to use the 1_3_0 Base |
|
53 | _reset_base(migrate_engine) | |
53 | Base = declarative_base() |
|
|||
54 | Base.metadata.clear() |
|
|||
55 | Base.metadata = MetaData() |
|
|||
56 | Base.metadata.bind = migrate_engine |
|
|||
57 | meta.Base = Base |
|
|||
58 |
|
54 | |||
59 | #========================================================================== |
|
55 | #========================================================================== | |
60 | # USERS TABLE |
|
56 | # USERS TABLE | |
@@ -173,12 +169,7 b' def upgrade(migrate_engine):' | |||||
173 | ForeignKey('pull_requests.pull_request_id'), |
|
169 | ForeignKey('pull_requests.pull_request_id'), | |
174 | nullable=True) |
|
170 | nullable=True) | |
175 | pull_request_id.create(table=tbl) |
|
171 | pull_request_id.create(table=tbl) | |
176 | ## RESET COMPLETLY THE metadata for sqlalchemy back after using 1_3_0 |
|
172 | _reset_base(migrate_engine) | |
177 | Base = declarative_base() |
|
|||
178 | Base.metadata.clear() |
|
|||
179 | Base.metadata = MetaData() |
|
|||
180 | Base.metadata.bind = migrate_engine |
|
|||
181 | meta.Base = Base |
|
|||
182 |
|
173 | |||
183 |
|
174 | |||
184 | def downgrade(migrate_engine): |
|
175 | def downgrade(migrate_engine): |
@@ -12,6 +12,7 b' from rhodecode.lib.dbmigrate.migrate.cha' | |||||
12 |
|
12 | |||
13 | from rhodecode.model.meta import Base |
|
13 | from rhodecode.model.meta import Base | |
14 | from rhodecode.model import meta |
|
14 | from rhodecode.model import meta | |
|
15 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
15 |
|
16 | |||
16 | log = logging.getLogger(__name__) |
|
17 | log = logging.getLogger(__name__) | |
17 |
|
18 | |||
@@ -24,6 +25,7 b' def upgrade(migrate_engine):' | |||||
24 | #========================================================================== |
|
25 | #========================================================================== | |
25 | # USER LOGS |
|
26 | # USER LOGS | |
26 | #========================================================================== |
|
27 | #========================================================================== | |
|
28 | _reset_base(migrate_engine) | |||
27 | from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserLog |
|
29 | from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserLog | |
28 | tbl = UserLog.__table__ |
|
30 | tbl = UserLog.__table__ | |
29 | username = Column("username", String(255, convert_unicode=False, |
|
31 | username = Column("username", String(255, convert_unicode=False, |
@@ -22,3 +22,23 b'' | |||||
22 | # |
|
22 | # | |
23 | # You should have received a copy of the GNU General Public License |
|
23 | # You should have received a copy of the GNU General Public License | |
24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
25 | from sqlalchemy import * | |||
|
26 | from sqlalchemy.exc import DatabaseError | |||
|
27 | from sqlalchemy.orm import relation, backref, class_mapper, joinedload | |||
|
28 | from sqlalchemy.orm.session import Session | |||
|
29 | from sqlalchemy.ext.declarative import declarative_base | |||
|
30 | ||||
|
31 | from rhodecode.lib.dbmigrate.migrate import * | |||
|
32 | from rhodecode.lib.dbmigrate.migrate.changeset import * | |||
|
33 | ||||
|
34 | from rhodecode.model.meta import Base | |||
|
35 | from rhodecode.model import meta | |||
|
36 | ||||
|
37 | ||||
|
38 | def _reset_base(migrate_engine): | |||
|
39 | ## RESET COMPLETLY THE metadata for sqlalchemy to use previous declared Base | |||
|
40 | Base = declarative_base() | |||
|
41 | Base.metadata.clear() | |||
|
42 | Base.metadata = MetaData() | |||
|
43 | Base.metadata.bind = migrate_engine | |||
|
44 | meta.Base = Base |
@@ -583,7 +583,7 b' class DiffProcessor(object):' | |||||
583 | #return u''.join(imap(self._line_counter, self._diff.splitlines(1))) |
|
583 | #return u''.join(imap(self._line_counter, self._diff.splitlines(1))) | |
584 |
|
584 | |||
585 | def as_html(self, table_class='code-difftable', line_class='line', |
|
585 | def as_html(self, table_class='code-difftable', line_class='line', | |
586 |
|
|
586 | old_lineno_class='lineno old', new_lineno_class='lineno new', | |
587 | code_class='code', enable_comments=False, parsed_lines=None): |
|
587 | code_class='code', enable_comments=False, parsed_lines=None): | |
588 | """ |
|
588 | """ | |
589 | Return given diff as html table with customized css classes |
|
589 | Return given diff as html table with customized css classes |
@@ -464,7 +464,7 b' def desc_stylize(value):' | |||||
464 | '<div class="metatag" tag="see">see => \\1 </div>', value) |
|
464 | '<div class="metatag" tag="see">see => \\1 </div>', value) | |
465 | value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', |
|
465 | value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', | |
466 | '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value) |
|
466 | '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value) | |
467 | value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]', |
|
467 | value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]', | |
468 | '<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value) |
|
468 | '<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value) | |
469 | value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', |
|
469 | value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]', | |
470 | '<div class="metatag" tag="lang">\\2</div>', value) |
|
470 | '<div class="metatag" tag="lang">\\2</div>', value) | |
@@ -1164,3 +1164,9 b' def not_mapped_error(repo_name):' | |||||
1164 | ' it was created or renamed from the filesystem' |
|
1164 | ' it was created or renamed from the filesystem' | |
1165 | ' please run the application again' |
|
1165 | ' please run the application again' | |
1166 | ' in order to rescan repositories') % repo_name, category='error') |
|
1166 | ' in order to rescan repositories') % repo_name, category='error') | |
|
1167 | ||||
|
1168 | ||||
|
1169 | def ip_range(ip_addr): | |||
|
1170 | from rhodecode.model.db import UserIpMap | |||
|
1171 | s, e = UserIpMap._get_ip_range(ip_addr) | |||
|
1172 | return '%s - %s' % (s, e) |
@@ -98,7 +98,7 b' class MarkupRenderer(object):' | |||||
98 | source = safe_unicode(source) |
|
98 | source = safe_unicode(source) | |
99 | try: |
|
99 | try: | |
100 | import markdown as __markdown |
|
100 | import markdown as __markdown | |
101 |
return __markdown.markdown(source, ['codehilite', 'ta |
|
101 | return __markdown.markdown(source, ['codehilite', 'extra']) | |
102 | except ImportError: |
|
102 | except ImportError: | |
103 | log.warning('Install markdown to use this function') |
|
103 | log.warning('Install markdown to use this function') | |
104 | return cls.plain(source) |
|
104 | return cls.plain(source) |
@@ -109,7 +109,7 b' class SimpleGit(BaseVCSController):' | |||||
109 | if not self._check_ssl(environ, start_response): |
|
109 | if not self._check_ssl(environ, start_response): | |
110 | return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) |
|
110 | return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) | |
111 |
|
111 | |||
112 | ipaddr = self._get_ip_addr(environ) |
|
112 | ip_addr = self._get_ip_addr(environ) | |
113 | username = None |
|
113 | username = None | |
114 | self._git_first_op = False |
|
114 | self._git_first_op = False | |
115 | # skip passing error to error controller |
|
115 | # skip passing error to error controller | |
@@ -140,7 +140,7 b' class SimpleGit(BaseVCSController):' | |||||
140 | anonymous_user = self.__get_user('default') |
|
140 | anonymous_user = self.__get_user('default') | |
141 | username = anonymous_user.username |
|
141 | username = anonymous_user.username | |
142 | anonymous_perm = self._check_permission(action, anonymous_user, |
|
142 | anonymous_perm = self._check_permission(action, anonymous_user, | |
143 | repo_name) |
|
143 | repo_name, ip_addr) | |
144 |
|
144 | |||
145 | if anonymous_perm is not True or anonymous_user.active is False: |
|
145 | if anonymous_perm is not True or anonymous_user.active is False: | |
146 | if anonymous_perm is not True: |
|
146 | if anonymous_perm is not True: | |
@@ -182,7 +182,7 b' class SimpleGit(BaseVCSController):' | |||||
182 | return HTTPInternalServerError()(environ, start_response) |
|
182 | return HTTPInternalServerError()(environ, start_response) | |
183 |
|
183 | |||
184 | #check permissions for this repository |
|
184 | #check permissions for this repository | |
185 | perm = self._check_permission(action, user, repo_name) |
|
185 | perm = self._check_permission(action, user, repo_name, ip_addr) | |
186 | if perm is not True: |
|
186 | if perm is not True: | |
187 | return HTTPForbidden()(environ, start_response) |
|
187 | return HTTPForbidden()(environ, start_response) | |
188 |
|
188 | |||
@@ -191,7 +191,7 b' class SimpleGit(BaseVCSController):' | |||||
191 | from rhodecode import CONFIG |
|
191 | from rhodecode import CONFIG | |
192 | server_url = get_server_url(environ) |
|
192 | server_url = get_server_url(environ) | |
193 | extras = { |
|
193 | extras = { | |
194 | 'ip': ipaddr, |
|
194 | 'ip': ip_addr, | |
195 | 'username': username, |
|
195 | 'username': username, | |
196 | 'action': action, |
|
196 | 'action': action, | |
197 | 'repository': repo_name, |
|
197 | 'repository': repo_name, | |
@@ -233,11 +233,12 b' class SimpleGit(BaseVCSController):' | |||||
233 | self._invalidate_cache(repo_name) |
|
233 | self._invalidate_cache(repo_name) | |
234 | self._handle_githooks(repo_name, action, baseui, environ) |
|
234 | self._handle_githooks(repo_name, action, baseui, environ) | |
235 |
|
235 | |||
236 |
log.info('%s action on GIT repo "%s"' % |
|
236 | log.info('%s action on GIT repo "%s" by "%s" from %s' % | |
|
237 | (action, repo_name, username, ip_addr)) | |||
237 | app = self.__make_app(repo_name, repo_path, extras) |
|
238 | app = self.__make_app(repo_name, repo_path, extras) | |
238 | return app(environ, start_response) |
|
239 | return app(environ, start_response) | |
239 | except HTTPLockedRC, e: |
|
240 | except HTTPLockedRC, e: | |
240 | log.debug('Repositry LOCKED ret code 423!') |
|
241 | log.debug('Repository LOCKED ret code 423!') | |
241 | return e(environ, start_response) |
|
242 | return e(environ, start_response) | |
242 | except Exception: |
|
243 | except Exception: | |
243 | log.error(traceback.format_exc()) |
|
244 | log.error(traceback.format_exc()) |
@@ -73,7 +73,7 b' class SimpleHg(BaseVCSController):' | |||||
73 | if not self._check_ssl(environ, start_response): |
|
73 | if not self._check_ssl(environ, start_response): | |
74 | return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) |
|
74 | return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) | |
75 |
|
75 | |||
76 | ipaddr = self._get_ip_addr(environ) |
|
76 | ip_addr = self._get_ip_addr(environ) | |
77 | username = None |
|
77 | username = None | |
78 | # skip passing error to error controller |
|
78 | # skip passing error to error controller | |
79 | environ['pylons.status_code_redirect'] = True |
|
79 | environ['pylons.status_code_redirect'] = True | |
@@ -103,7 +103,7 b' class SimpleHg(BaseVCSController):' | |||||
103 | anonymous_user = self.__get_user('default') |
|
103 | anonymous_user = self.__get_user('default') | |
104 | username = anonymous_user.username |
|
104 | username = anonymous_user.username | |
105 | anonymous_perm = self._check_permission(action, anonymous_user, |
|
105 | anonymous_perm = self._check_permission(action, anonymous_user, | |
106 | repo_name) |
|
106 | repo_name, ip_addr) | |
107 |
|
107 | |||
108 | if anonymous_perm is not True or anonymous_user.active is False: |
|
108 | if anonymous_perm is not True or anonymous_user.active is False: | |
109 | if anonymous_perm is not True: |
|
109 | if anonymous_perm is not True: | |
@@ -145,7 +145,7 b' class SimpleHg(BaseVCSController):' | |||||
145 | return HTTPInternalServerError()(environ, start_response) |
|
145 | return HTTPInternalServerError()(environ, start_response) | |
146 |
|
146 | |||
147 | #check permissions for this repository |
|
147 | #check permissions for this repository | |
148 | perm = self._check_permission(action, user, repo_name) |
|
148 | perm = self._check_permission(action, user, repo_name, ip_addr) | |
149 | if perm is not True: |
|
149 | if perm is not True: | |
150 | return HTTPForbidden()(environ, start_response) |
|
150 | return HTTPForbidden()(environ, start_response) | |
151 |
|
151 | |||
@@ -154,7 +154,7 b' class SimpleHg(BaseVCSController):' | |||||
154 | from rhodecode import CONFIG |
|
154 | from rhodecode import CONFIG | |
155 | server_url = get_server_url(environ) |
|
155 | server_url = get_server_url(environ) | |
156 | extras = { |
|
156 | extras = { | |
157 | 'ip': ipaddr, |
|
157 | 'ip': ip_addr, | |
158 | 'username': username, |
|
158 | 'username': username, | |
159 | 'action': action, |
|
159 | 'action': action, | |
160 | 'repository': repo_name, |
|
160 | 'repository': repo_name, | |
@@ -194,14 +194,15 b' class SimpleHg(BaseVCSController):' | |||||
194 | # invalidate cache on push |
|
194 | # invalidate cache on push | |
195 | if action == 'push': |
|
195 | if action == 'push': | |
196 | self._invalidate_cache(repo_name) |
|
196 | self._invalidate_cache(repo_name) | |
197 |
log.info('%s action on HG repo "%s"' % |
|
197 | log.info('%s action on HG repo "%s" by "%s" from %s' % | |
|
198 | (action, repo_name, username, ip_addr)) | |||
198 | app = self.__make_app(repo_path, baseui, extras) |
|
199 | app = self.__make_app(repo_path, baseui, extras) | |
199 | return app(environ, start_response) |
|
200 | return app(environ, start_response) | |
200 | except RepoError, e: |
|
201 | except RepoError, e: | |
201 | if str(e).find('not found') != -1: |
|
202 | if str(e).find('not found') != -1: | |
202 | return HTTPNotFound()(environ, start_response) |
|
203 | return HTTPNotFound()(environ, start_response) | |
203 | except HTTPLockedRC, e: |
|
204 | except HTTPLockedRC, e: | |
204 | log.debug('Repositry LOCKED ret code 423!') |
|
205 | log.debug('Repository LOCKED ret code 423!') | |
205 | return e(environ, start_response) |
|
206 | return e(environ, start_response) | |
206 | except Exception: |
|
207 | except Exception: | |
207 | log.error(traceback.format_exc()) |
|
208 | log.error(traceback.format_exc()) |
@@ -34,6 +34,7 b' from os.path import dirname as dn, join ' | |||||
34 | from rhodecode.model import init_model |
|
34 | from rhodecode.model import init_model | |
35 | from rhodecode.lib.utils2 import engine_from_config, safe_str |
|
35 | from rhodecode.lib.utils2 import engine_from_config, safe_str | |
36 | from rhodecode.model.db import RhodeCodeUi, Repository |
|
36 | from rhodecode.model.db import RhodeCodeUi, Repository | |
|
37 | from rhodecode.lib.vcs.backends.base import EmptyChangeset | |||
37 |
|
38 | |||
38 |
|
39 | |||
39 | #to get the rhodecode import |
|
40 | #to get the rhodecode import | |
@@ -73,8 +74,9 b' class UpdateCommand(BasePasterCommand):' | |||||
73 | else: |
|
74 | else: | |
74 | repo_list = Repository.getAll() |
|
75 | repo_list = Repository.getAll() | |
75 | for repo in repo_list: |
|
76 | for repo in repo_list: | |
76 |
last_c |
|
77 | last_cs = (repo.scm_instance.get_changeset() if repo.scm_instance | |
77 | repo.update_last_change(last_change) |
|
78 | else EmptyChangeset()) | |
|
79 | repo.update_changeset_cache(last_cs) | |||
78 |
|
80 | |||
79 | def update_parser(self): |
|
81 | def update_parser(self): | |
80 | self.parser.add_option('--update-only', |
|
82 | self.parser.add_option('--update-only', |
@@ -162,10 +162,8 b' def action_logger(user, action, repo, ip' | |||||
162 | user_log.user_ip = ipaddr |
|
162 | user_log.user_ip = ipaddr | |
163 | sa.add(user_log) |
|
163 | sa.add(user_log) | |
164 |
|
164 | |||
165 | log.info( |
|
165 | log.info('Logging action %s on %s by %s' % | |
166 | 'Adding user %s, action %s on %s' % (user_obj, action, |
|
166 | (action, safe_unicode(repo), user_obj)) | |
167 | safe_unicode(repo)) |
|
|||
168 | ) |
|
|||
169 | if commit: |
|
167 | if commit: | |
170 | sa.commit() |
|
168 | sa.commit() | |
171 | except: |
|
169 | except: | |
@@ -309,7 +307,7 b" def make_ui(read_from='file', path=None," | |||||
309 | cfg.read(path) |
|
307 | cfg.read(path) | |
310 | for section in ui_sections: |
|
308 | for section in ui_sections: | |
311 | for k, v in cfg.items(section): |
|
309 | for k, v in cfg.items(section): | |
312 |
log.debug('settings ui from file[%s]%s |
|
310 | log.debug('settings ui from file: [%s] %s=%s' % (section, k, v)) | |
313 | baseui.setconfig(safe_str(section), safe_str(k), safe_str(v)) |
|
311 | baseui.setconfig(safe_str(section), safe_str(k), safe_str(v)) | |
314 |
|
312 | |||
315 | elif read_from == 'db': |
|
313 | elif read_from == 'db': | |
@@ -321,7 +319,7 b" def make_ui(read_from='file', path=None," | |||||
321 | hg_ui = ret |
|
319 | hg_ui = ret | |
322 | for ui_ in hg_ui: |
|
320 | for ui_ in hg_ui: | |
323 | if ui_.ui_active: |
|
321 | if ui_.ui_active: | |
324 |
log.debug('settings ui from db[%s]%s |
|
322 | log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section, | |
325 | ui_.ui_key, ui_.ui_value) |
|
323 | ui_.ui_key, ui_.ui_value) | |
326 | baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key), |
|
324 | baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key), | |
327 | safe_str(ui_.ui_value)) |
|
325 | safe_str(ui_.ui_value)) | |
@@ -423,6 +421,13 b' def repo2db_mapper(initial_repo_list, re' | |||||
423 | # CacheInvalidation.clear_cache() |
|
421 | # CacheInvalidation.clear_cache() | |
424 | # sa.commit() |
|
422 | # sa.commit() | |
425 |
|
423 | |||
|
424 | ##creation defaults | |||
|
425 | defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) | |||
|
426 | enable_statistics = defs.get('repo_enable_statistics') | |||
|
427 | enable_locking = defs.get('repo_enable_locking') | |||
|
428 | enable_downloads = defs.get('repo_enable_downloads') | |||
|
429 | private = defs.get('repo_private') | |||
|
430 | ||||
426 | for name, repo in initial_repo_list.items(): |
|
431 | for name, repo in initial_repo_list.items(): | |
427 | group = map_groups(name) |
|
432 | group = map_groups(name) | |
428 | db_repo = rm.get_by_repo_name(name) |
|
433 | db_repo = rm.get_by_repo_name(name) | |
@@ -433,18 +438,24 b' def repo2db_mapper(initial_repo_list, re' | |||||
433 | desc = (repo.description |
|
438 | desc = (repo.description | |
434 | if repo.description != 'unknown' |
|
439 | if repo.description != 'unknown' | |
435 | else '%s repository' % name) |
|
440 | else '%s repository' % name) | |
|
441 | ||||
436 | new_repo = rm.create_repo( |
|
442 | new_repo = rm.create_repo( | |
437 | repo_name=name, |
|
443 | repo_name=name, | |
438 | repo_type=repo.alias, |
|
444 | repo_type=repo.alias, | |
439 | description=desc, |
|
445 | description=desc, | |
440 | repos_group=getattr(group, 'group_id', None), |
|
446 | repos_group=getattr(group, 'group_id', None), | |
441 | owner=user, |
|
447 | owner=user, | |
442 | just_db=True |
|
448 | just_db=True, | |
|
449 | enable_locking=enable_locking, | |||
|
450 | enable_downloads=enable_downloads, | |||
|
451 | enable_statistics=enable_statistics, | |||
|
452 | private=private | |||
443 | ) |
|
453 | ) | |
444 | # we added that repo just now, and make sure it has githook |
|
454 | # we added that repo just now, and make sure it has githook | |
445 | # installed |
|
455 | # installed | |
446 | if new_repo.repo_type == 'git': |
|
456 | if new_repo.repo_type == 'git': | |
447 | ScmModel().install_git_hook(new_repo.scm_instance) |
|
457 | ScmModel().install_git_hook(new_repo.scm_instance) | |
|
458 | new_repo.update_changeset_cache() | |||
448 | elif install_git_hook: |
|
459 | elif install_git_hook: | |
449 | if db_repo.repo_type == 'git': |
|
460 | if db_repo.repo_type == 'git': | |
450 | ScmModel().install_git_hook(db_repo.scm_instance) |
|
461 | ScmModel().install_git_hook(db_repo.scm_instance) | |
@@ -452,8 +463,8 b' def repo2db_mapper(initial_repo_list, re' | |||||
452 | # system, this will register all repos and multiple instances |
|
463 | # system, this will register all repos and multiple instances | |
453 | key, _prefix, _org_key = CacheInvalidation._get_key(name) |
|
464 | key, _prefix, _org_key = CacheInvalidation._get_key(name) | |
454 | CacheInvalidation.invalidate(name) |
|
465 | CacheInvalidation.invalidate(name) | |
455 |
log.debug("Creating a cache key for %s instance_id |
|
466 | log.debug("Creating a cache key for %s, instance_id %s" | |
456 |
% (name, _prefix or ' |
|
467 | % (name, _prefix or 'unknown')) | |
457 |
|
468 | |||
458 | sa.commit() |
|
469 | sa.commit() | |
459 | removed = [] |
|
470 | removed = [] | |
@@ -740,4 +751,4 b' def jsonify(func, *args, **kwargs):' | |||||
740 | warnings.warn(msg, Warning, 2) |
|
751 | warnings.warn(msg, Warning, 2) | |
741 | log.warning(msg) |
|
752 | log.warning(msg) | |
742 | log.debug("Returning JSON wrapped action output") |
|
753 | log.debug("Returning JSON wrapped action output") | |
743 | return json.dumps(data, encoding='utf-8') No newline at end of file |
|
754 | return json.dumps(data, encoding='utf-8') |
@@ -376,6 +376,7 b' class BaseChangeset(object):' | |||||
376 | return dict( |
|
376 | return dict( | |
377 | short_id=self.short_id, |
|
377 | short_id=self.short_id, | |
378 | raw_id=self.raw_id, |
|
378 | raw_id=self.raw_id, | |
|
379 | revision=self.revision, | |||
379 | message=self.message, |
|
380 | message=self.message, | |
380 | date=self.date, |
|
381 | date=self.date, | |
381 | author=self.author, |
|
382 | author=self.author, |
@@ -606,10 +606,13 b' class GitRepository(BaseRepository):' | |||||
606 | Tries to pull changes from external location. |
|
606 | Tries to pull changes from external location. | |
607 | """ |
|
607 | """ | |
608 | url = self._get_url(url) |
|
608 | url = self._get_url(url) | |
609 | cmd = ['fetch'] |
|
609 | so, se = self.run_git_command('ls-remote -h %s' % url) | |
610 | cmd.append(url) |
|
610 | refs = [] | |
611 | cmd = ' '.join(cmd) |
|
611 | for line in (x for x in so.splitlines()): | |
612 | # If error occurs run_git_command raises RepositoryError already |
|
612 | sha, ref = line.split('\t') | |
|
613 | refs.append(ref) | |||
|
614 | refs = ' '.join(('+%s:%s' % (r, r) for r in refs)) | |||
|
615 | cmd = '''fetch %s -- %s''' % (url, refs) | |||
613 | self.run_git_command(cmd) |
|
616 | self.run_git_command(cmd) | |
614 |
|
617 | |||
615 | @LazyProperty |
|
618 | @LazyProperty |
@@ -362,10 +362,11 b' class FileNode(Node):' | |||||
362 | Returns pygment's lexer class. Would try to guess lexer taking file's |
|
362 | Returns pygment's lexer class. Would try to guess lexer taking file's | |
363 | content, name and mimetype. |
|
363 | content, name and mimetype. | |
364 | """ |
|
364 | """ | |
|
365 | ||||
365 | try: |
|
366 | try: | |
366 | lexer = lexers.guess_lexer_for_filename(self.name, self.content) |
|
367 | lexer = lexers.guess_lexer_for_filename(self.name, self.content, stripnl=False) | |
367 | except lexers.ClassNotFound: |
|
368 | except lexers.ClassNotFound: | |
368 | lexer = lexers.TextLexer() |
|
369 | lexer = lexers.TextLexer(stripnl=False) | |
369 | # returns first alias |
|
370 | # returns first alias | |
370 | return lexer |
|
371 | return lexer | |
371 |
|
372 |
@@ -89,44 +89,39 b' class ChangesetStatusModel(BaseModel):' | |||||
89 | with_revisions) |
|
89 | with_revisions) | |
90 | return q.all() |
|
90 | return q.all() | |
91 |
|
91 | |||
92 | def get_status(self, repo, revision=None, pull_request=None): |
|
92 | def get_status(self, repo, revision=None, pull_request=None, as_str=True): | |
93 | """ |
|
93 | """ | |
94 | Returns latest status of changeset for given revision or for given |
|
94 | Returns latest status of changeset for given revision or for given | |
95 | pull request. Statuses are versioned inside a table itself and |
|
95 | pull request. Statuses are versioned inside a table itself and | |
96 | version == 0 is always the current one |
|
96 | version == 0 is always the current one | |
97 |
|
97 | |||
98 | :param repo: |
|
98 | :param repo: | |
99 | :type repo: |
|
|||
100 | :param revision: 40char hash or None |
|
99 | :param revision: 40char hash or None | |
101 | :type revision: str |
|
|||
102 | :param pull_request: pull_request reference |
|
100 | :param pull_request: pull_request reference | |
103 | :type: |
|
101 | :param as_str: return status as string not object | |
104 | """ |
|
102 | """ | |
105 | q = self._get_status_query(repo, revision, pull_request) |
|
103 | q = self._get_status_query(repo, revision, pull_request) | |
106 |
|
104 | |||
107 | # need to use first here since there can be multiple statuses |
|
105 | # need to use first here since there can be multiple statuses | |
108 | # returned from pull_request |
|
106 | # returned from pull_request | |
109 | status = q.first() |
|
107 | status = q.first() | |
110 | status = status.status if status else status |
|
108 | if as_str: | |
111 | st = status or ChangesetStatus.DEFAULT |
|
109 | status = status.status if status else status | |
112 | return str(st) |
|
110 | st = status or ChangesetStatus.DEFAULT | |
|
111 | return str(st) | |||
|
112 | return status | |||
113 |
|
113 | |||
114 | def set_status(self, repo, status, user, comment, revision=None, |
|
114 | def set_status(self, repo, status, user, comment=None, revision=None, | |
115 | pull_request=None, dont_allow_on_closed_pull_request=False): |
|
115 | pull_request=None, dont_allow_on_closed_pull_request=False): | |
116 | """ |
|
116 | """ | |
117 | Creates new status for changeset or updates the old ones bumping their |
|
117 | Creates new status for changeset or updates the old ones bumping their | |
118 | version, leaving the current status at |
|
118 | version, leaving the current status at | |
119 |
|
119 | |||
120 | :param repo: |
|
120 | :param repo: | |
121 | :type repo: |
|
|||
122 | :param revision: |
|
121 | :param revision: | |
123 | :type revision: |
|
|||
124 | :param status: |
|
122 | :param status: | |
125 | :type status: |
|
|||
126 | :param user: |
|
123 | :param user: | |
127 | :type user: |
|
|||
128 | :param comment: |
|
124 | :param comment: | |
129 | :type comment: |
|
|||
130 | :param dont_allow_on_closed_pull_request: don't allow a status change |
|
125 | :param dont_allow_on_closed_pull_request: don't allow a status change | |
131 | if last status was for pull request and it's closed. We shouldn't |
|
126 | if last status was for pull request and it's closed. We shouldn't | |
132 | mess around this manually |
|
127 | mess around this manually | |
@@ -134,14 +129,21 b' class ChangesetStatusModel(BaseModel):' | |||||
134 | repo = self._get_repo(repo) |
|
129 | repo = self._get_repo(repo) | |
135 |
|
130 | |||
136 | q = ChangesetStatus.query() |
|
131 | q = ChangesetStatus.query() | |
137 |
|
132 | if not comment: | ||
|
133 | from rhodecode.model.comment import ChangesetCommentsModel | |||
|
134 | comment = ChangesetCommentsModel().create( | |||
|
135 | text='Auto status change', | |||
|
136 | repo=repo, | |||
|
137 | user=user, | |||
|
138 | pull_request=pull_request, | |||
|
139 | ) | |||
138 | if revision: |
|
140 | if revision: | |
139 | q = q.filter(ChangesetStatus.repo == repo) |
|
141 | q = q.filter(ChangesetStatus.repo == repo) | |
140 | q = q.filter(ChangesetStatus.revision == revision) |
|
142 | q = q.filter(ChangesetStatus.revision == revision) | |
141 | elif pull_request: |
|
143 | elif pull_request: | |
142 | pull_request = self.__get_pull_request(pull_request) |
|
144 | pull_request = self.__get_pull_request(pull_request) | |
143 | q = q.filter(ChangesetStatus.repo == pull_request.org_repo) |
|
145 | q = q.filter(ChangesetStatus.repo == pull_request.org_repo) | |
144 |
q = q.filter(ChangesetStatus.pull_request |
|
146 | q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions)) | |
145 | cur_statuses = q.all() |
|
147 | cur_statuses = q.all() | |
146 |
|
148 | |||
147 | #if statuses exists and last is associated with a closed pull request |
|
149 | #if statuses exists and last is associated with a closed pull request | |
@@ -153,6 +155,7 b' class ChangesetStatusModel(BaseModel):' | |||||
153 | 'Changing status on closed pull request is not allowed' |
|
155 | 'Changing status on closed pull request is not allowed' | |
154 | ) |
|
156 | ) | |
155 |
|
157 | |||
|
158 | #update all current statuses with older version | |||
156 | if cur_statuses: |
|
159 | if cur_statuses: | |
157 | for st in cur_statuses: |
|
160 | for st in cur_statuses: | |
158 | st.version += 1 |
|
161 | st.version += 1 |
@@ -370,6 +370,11 b' class User(Base, BaseModel):' | |||||
370 | return [self.email] + [x.email for x in other] |
|
370 | return [self.email] + [x.email for x in other] | |
371 |
|
371 | |||
372 | @property |
|
372 | @property | |
|
373 | def ip_addresses(self): | |||
|
374 | ret = UserIpMap.query().filter(UserIpMap.user == self).all() | |||
|
375 | return [x.ip_addr for x in ret] | |||
|
376 | ||||
|
377 | @property | |||
373 | def username_and_name(self): |
|
378 | def username_and_name(self): | |
374 | return '%s (%s %s)' % (self.username, self.firstname, self.lastname) |
|
379 | return '%s (%s %s)' % (self.username, self.firstname, self.lastname) | |
375 |
|
380 | |||
@@ -472,6 +477,7 b' class User(Base, BaseModel):' | |||||
472 | admin=user.admin, |
|
477 | admin=user.admin, | |
473 | ldap_dn=user.ldap_dn, |
|
478 | ldap_dn=user.ldap_dn, | |
474 | last_login=user.last_login, |
|
479 | last_login=user.last_login, | |
|
480 | ip_addresses=user.ip_addresses | |||
475 | ) |
|
481 | ) | |
476 | return data |
|
482 | return data | |
477 |
|
483 | |||
@@ -518,6 +524,34 b' class UserEmailMap(Base, BaseModel):' | |||||
518 | self._email = val.lower() if val else None |
|
524 | self._email = val.lower() if val else None | |
519 |
|
525 | |||
520 |
|
526 | |||
|
527 | class UserIpMap(Base, BaseModel): | |||
|
528 | __tablename__ = 'user_ip_map' | |||
|
529 | __table_args__ = ( | |||
|
530 | UniqueConstraint('user_id', 'ip_addr'), | |||
|
531 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |||
|
532 | 'mysql_charset': 'utf8'} | |||
|
533 | ) | |||
|
534 | __mapper_args__ = {} | |||
|
535 | ||||
|
536 | ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |||
|
537 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |||
|
538 | ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |||
|
539 | active = Column("active", Boolean(), nullable=True, unique=None, default=True) | |||
|
540 | user = relationship('User', lazy='joined') | |||
|
541 | ||||
|
542 | @classmethod | |||
|
543 | def _get_ip_range(cls, ip_addr): | |||
|
544 | from rhodecode.lib import ipaddr | |||
|
545 | net = ipaddr.IPv4Network(ip_addr) | |||
|
546 | return [str(net.network), str(net.broadcast)] | |||
|
547 | ||||
|
548 | def __json__(self): | |||
|
549 | return dict( | |||
|
550 | ip_addr=self.ip_addr, | |||
|
551 | ip_range=self._get_ip_range(self.ip_addr) | |||
|
552 | ) | |||
|
553 | ||||
|
554 | ||||
521 | class UserLog(Base, BaseModel): |
|
555 | class UserLog(Base, BaseModel): | |
522 | __tablename__ = 'user_logs' |
|
556 | __tablename__ = 'user_logs' | |
523 | __table_args__ = ( |
|
557 | __table_args__ = ( | |
@@ -637,6 +671,7 b' class Repository(Base, BaseModel):' | |||||
637 | landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) |
|
671 | landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |
638 | enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) |
|
672 | enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) | |
639 | _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) |
|
673 | _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |
|
674 | _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data | |||
640 |
|
675 | |||
641 | fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) |
|
676 | fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) | |
642 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) |
|
677 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) | |
@@ -682,11 +717,40 b' class Repository(Base, BaseModel):' | |||||
682 | else: |
|
717 | else: | |
683 | self._locked = None |
|
718 | self._locked = None | |
684 |
|
719 | |||
|
720 | @hybrid_property | |||
|
721 | def changeset_cache(self): | |||
|
722 | from rhodecode.lib.vcs.backends.base import EmptyChangeset | |||
|
723 | dummy = EmptyChangeset().__json__() | |||
|
724 | if not self._changeset_cache: | |||
|
725 | return dummy | |||
|
726 | try: | |||
|
727 | return json.loads(self._changeset_cache) | |||
|
728 | except TypeError: | |||
|
729 | return dummy | |||
|
730 | ||||
|
731 | @changeset_cache.setter | |||
|
732 | def changeset_cache(self, val): | |||
|
733 | try: | |||
|
734 | self._changeset_cache = json.dumps(val) | |||
|
735 | except: | |||
|
736 | log.error(traceback.format_exc()) | |||
|
737 | ||||
685 | @classmethod |
|
738 | @classmethod | |
686 | def url_sep(cls): |
|
739 | def url_sep(cls): | |
687 | return URL_SEP |
|
740 | return URL_SEP | |
688 |
|
741 | |||
689 | @classmethod |
|
742 | @classmethod | |
|
743 | def normalize_repo_name(cls, repo_name): | |||
|
744 | """ | |||
|
745 | Normalizes os specific repo_name to the format internally stored inside | |||
|
746 | dabatabase using URL_SEP | |||
|
747 | ||||
|
748 | :param cls: | |||
|
749 | :param repo_name: | |||
|
750 | """ | |||
|
751 | return cls.url_sep().join(repo_name.split(os.sep)) | |||
|
752 | ||||
|
753 | @classmethod | |||
690 | def get_by_repo_name(cls, repo_name): |
|
754 | def get_by_repo_name(cls, repo_name): | |
691 | q = Session().query(cls).filter(cls.repo_name == repo_name) |
|
755 | q = Session().query(cls).filter(cls.repo_name == repo_name) | |
692 | q = q.options(joinedload(Repository.fork))\ |
|
756 | q = q.options(joinedload(Repository.fork))\ | |
@@ -697,6 +761,7 b' class Repository(Base, BaseModel):' | |||||
697 | @classmethod |
|
761 | @classmethod | |
698 | def get_by_full_path(cls, repo_full_path): |
|
762 | def get_by_full_path(cls, repo_full_path): | |
699 | repo_name = repo_full_path.split(cls.base_path(), 1)[-1] |
|
763 | repo_name = repo_full_path.split(cls.base_path(), 1)[-1] | |
|
764 | repo_name = cls.normalize_repo_name(repo_name) | |||
700 | return cls.get_by_repo_name(repo_name.strip(URL_SEP)) |
|
765 | return cls.get_by_repo_name(repo_name.strip(URL_SEP)) | |
701 |
|
766 | |||
702 | @classmethod |
|
767 | @classmethod | |
@@ -841,7 +906,11 b' class Repository(Base, BaseModel):' | |||||
841 | description=repo.description, |
|
906 | description=repo.description, | |
842 | landing_rev=repo.landing_rev, |
|
907 | landing_rev=repo.landing_rev, | |
843 | owner=repo.user.username, |
|
908 | owner=repo.user.username, | |
844 | fork_of=repo.fork.repo_name if repo.fork else None |
|
909 | fork_of=repo.fork.repo_name if repo.fork else None, | |
|
910 | enable_statistics=repo.enable_statistics, | |||
|
911 | enable_locking=repo.enable_locking, | |||
|
912 | enable_downloads=repo.enable_downloads, | |||
|
913 | last_changeset=repo.changeset_cache | |||
845 | ) |
|
914 | ) | |
846 |
|
915 | |||
847 | return data |
|
916 | return data | |
@@ -862,6 +931,25 b' class Repository(Base, BaseModel):' | |||||
862 | def last_db_change(self): |
|
931 | def last_db_change(self): | |
863 | return self.updated_on |
|
932 | return self.updated_on | |
864 |
|
933 | |||
|
934 | def clone_url(self, **override): | |||
|
935 | from pylons import url | |||
|
936 | from urlparse import urlparse | |||
|
937 | import urllib | |||
|
938 | parsed_url = urlparse(url('home', qualified=True)) | |||
|
939 | default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' | |||
|
940 | decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) | |||
|
941 | args = { | |||
|
942 | 'user': '', | |||
|
943 | 'pass': '', | |||
|
944 | 'scheme': parsed_url.scheme, | |||
|
945 | 'netloc': parsed_url.netloc, | |||
|
946 | 'prefix': decoded_path, | |||
|
947 | 'path': self.repo_name | |||
|
948 | } | |||
|
949 | ||||
|
950 | args.update(override) | |||
|
951 | return default_clone_uri % args | |||
|
952 | ||||
865 | #========================================================================== |
|
953 | #========================================================================== | |
866 | # SCM PROPERTIES |
|
954 | # SCM PROPERTIES | |
867 | #========================================================================== |
|
955 | #========================================================================== | |
@@ -876,12 +964,30 b' class Repository(Base, BaseModel):' | |||||
876 | cs = self.get_changeset(self.landing_rev) or self.get_changeset() |
|
964 | cs = self.get_changeset(self.landing_rev) or self.get_changeset() | |
877 | return cs |
|
965 | return cs | |
878 |
|
966 | |||
879 |
def update_ |
|
967 | def update_changeset_cache(self, cs_cache=None): | |
880 | if last_change is None: |
|
968 | """ | |
881 | last_change = datetime.datetime.now() |
|
969 | Update cache of last changeset for repository, keys should be:: | |
882 | if self.updated_on is None or self.updated_on != last_change: |
|
970 | ||
883 | log.debug('updated repo %s with new date %s' % (self, last_change)) |
|
971 | short_id | |
|
972 | raw_id | |||
|
973 | revision | |||
|
974 | message | |||
|
975 | date | |||
|
976 | author | |||
|
977 | ||||
|
978 | :param cs_cache: | |||
|
979 | """ | |||
|
980 | from rhodecode.lib.vcs.backends.base import BaseChangeset | |||
|
981 | if cs_cache is None: | |||
|
982 | cs_cache = self.get_changeset() | |||
|
983 | if isinstance(cs_cache, BaseChangeset): | |||
|
984 | cs_cache = cs_cache.__json__() | |||
|
985 | ||||
|
986 | if cs_cache != self.changeset_cache: | |||
|
987 | last_change = cs_cache.get('date') or self.last_change | |||
|
988 | log.debug('updated repo %s with new cs cache %s' % (self, cs_cache)) | |||
884 | self.updated_on = last_change |
|
989 | self.updated_on = last_change | |
|
990 | self.changeset_cache = cs_cache | |||
885 | Session().add(self) |
|
991 | Session().add(self) | |
886 | Session().commit() |
|
992 | Session().commit() | |
887 |
|
993 | |||
@@ -1708,6 +1814,14 b' class PullRequest(Base, BaseModel):' | |||||
1708 | def revisions(self, val): |
|
1814 | def revisions(self, val): | |
1709 | self._revisions = ':'.join(val) |
|
1815 | self._revisions = ':'.join(val) | |
1710 |
|
1816 | |||
|
1817 | @property | |||
|
1818 | def org_ref_parts(self): | |||
|
1819 | return self.org_ref.split(':') | |||
|
1820 | ||||
|
1821 | @property | |||
|
1822 | def other_ref_parts(self): | |||
|
1823 | return self.other_ref.split(':') | |||
|
1824 | ||||
1711 | author = relationship('User', lazy='joined') |
|
1825 | author = relationship('User', lazy='joined') | |
1712 | reviewers = relationship('PullRequestReviewers', |
|
1826 | reviewers = relationship('PullRequestReviewers', | |
1713 | cascade="all, delete, delete-orphan") |
|
1827 | cascade="all, delete, delete-orphan") |
@@ -345,9 +345,14 b' def LdapSettingsForm(tls_reqcert_choices' | |||||
345 |
|
345 | |||
346 | def UserExtraEmailForm(): |
|
346 | def UserExtraEmailForm(): | |
347 | class _UserExtraEmailForm(formencode.Schema): |
|
347 | class _UserExtraEmailForm(formencode.Schema): | |
348 | email = All(v.UniqSystemEmail(), v.Email) |
|
348 | email = All(v.UniqSystemEmail(), v.Email(not_empty=True)) | |
|
349 | return _UserExtraEmailForm | |||
|
350 | ||||
349 |
|
351 | |||
350 | return _UserExtraEmailForm |
|
352 | def UserExtraIpForm(): | |
|
353 | class _UserExtraIpForm(formencode.Schema): | |||
|
354 | ip = v.ValidIp()(not_empty=True) | |||
|
355 | return _UserExtraIpForm | |||
351 |
|
356 | |||
352 |
|
357 | |||
353 | def PullRequestForm(repo_id): |
|
358 | def PullRequestForm(repo_id): | |
@@ -360,7 +365,8 b' def PullRequestForm(repo_id):' | |||||
360 | org_ref = v.UnicodeString(strip=True, required=True) |
|
365 | org_ref = v.UnicodeString(strip=True, required=True) | |
361 | other_repo = v.UnicodeString(strip=True, required=True) |
|
366 | other_repo = v.UnicodeString(strip=True, required=True) | |
362 | other_ref = v.UnicodeString(strip=True, required=True) |
|
367 | other_ref = v.UnicodeString(strip=True, required=True) | |
363 |
revisions = All(v.NotReviewedRevisions(repo_id)(), |
|
368 | revisions = All(#v.NotReviewedRevisions(repo_id)(), | |
|
369 | v.UniqueList(not_empty=True)) | |||
364 | review_members = v.UniqueList(not_empty=True) |
|
370 | review_members = v.UniqueList(not_empty=True) | |
365 |
|
371 | |||
366 | pullrequest_title = v.UnicodeString(strip=True, required=True, min=3) |
|
372 | pullrequest_title = v.UnicodeString(strip=True, required=True, min=3) |
@@ -270,8 +270,9 b' class EmailNotificationModel(BaseModel):' | |||||
270 |
|
270 | |||
271 | base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT]) |
|
271 | base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT]) | |
272 | email_template = self._tmpl_lookup.get_template(base) |
|
272 | email_template = self._tmpl_lookup.get_template(base) | |
273 | # translator inject |
|
273 | # translator and helpers inject | |
274 |
_kwargs = {'_': _ |
|
274 | _kwargs = {'_': _, | |
|
275 | 'h': h} | |||
275 | _kwargs.update(kwargs) |
|
276 | _kwargs.update(kwargs) | |
276 | log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs)) |
|
277 | log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs)) | |
277 | return email_template.render(**_kwargs) |
|
278 | return email_template.render(**_kwargs) |
@@ -33,7 +33,8 b' from pylons.i18n.translation import _' | |||||
33 | from rhodecode.model.meta import Session |
|
33 | from rhodecode.model.meta import Session | |
34 | from rhodecode.lib import helpers as h |
|
34 | from rhodecode.lib import helpers as h | |
35 | from rhodecode.model import BaseModel |
|
35 | from rhodecode.model import BaseModel | |
36 | from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification |
|
36 | from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\ | |
|
37 | ChangesetStatus | |||
37 | from rhodecode.model.notification import NotificationModel |
|
38 | from rhodecode.model.notification import NotificationModel | |
38 | from rhodecode.lib.utils2 import safe_unicode |
|
39 | from rhodecode.lib.utils2 import safe_unicode | |
39 |
|
40 | |||
@@ -54,8 +55,9 b' class PullRequestModel(BaseModel):' | |||||
54 | repo = self._get_repo(repo) |
|
55 | repo = self._get_repo(repo) | |
55 | return PullRequest.query().filter(PullRequest.other_repo == repo).all() |
|
56 | return PullRequest.query().filter(PullRequest.other_repo == repo).all() | |
56 |
|
57 | |||
57 | def create(self, created_by, org_repo, org_ref, other_repo, |
|
58 | def create(self, created_by, org_repo, org_ref, other_repo, other_ref, | |
58 |
|
|
59 | revisions, reviewers, title, description=None): | |
|
60 | from rhodecode.model.changeset_status import ChangesetStatusModel | |||
59 |
|
61 | |||
60 | created_by_user = self._get_user(created_by) |
|
62 | created_by_user = self._get_user(created_by) | |
61 | org_repo = self._get_repo(org_repo) |
|
63 | org_repo = self._get_repo(org_repo) | |
@@ -78,6 +80,14 b' class PullRequestModel(BaseModel):' | |||||
78 | reviewer = PullRequestReviewers(_usr, new) |
|
80 | reviewer = PullRequestReviewers(_usr, new) | |
79 | self.sa.add(reviewer) |
|
81 | self.sa.add(reviewer) | |
80 |
|
82 | |||
|
83 | #reset state to under-review | |||
|
84 | ChangesetStatusModel().set_status( | |||
|
85 | repo=org_repo, | |||
|
86 | status=ChangesetStatus.STATUS_UNDER_REVIEW, | |||
|
87 | user=created_by_user, | |||
|
88 | pull_request=new | |||
|
89 | ) | |||
|
90 | ||||
81 | #notification to reviewers |
|
91 | #notification to reviewers | |
82 | notif = NotificationModel() |
|
92 | notif = NotificationModel() | |
83 |
|
93 |
@@ -41,6 +41,7 b' from rhodecode.model.db import Repositor' | |||||
41 | Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup,\ |
|
41 | Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup,\ | |
42 | RhodeCodeSetting |
|
42 | RhodeCodeSetting | |
43 | from rhodecode.lib import helpers as h |
|
43 | from rhodecode.lib import helpers as h | |
|
44 | from rhodecode.lib.auth import HasRepoPermissionAny | |||
44 |
|
45 | |||
45 |
|
46 | |||
46 | log = logging.getLogger(__name__) |
|
47 | log = logging.getLogger(__name__) | |
@@ -89,6 +90,22 b' class RepoModel(BaseModel):' | |||||
89 | "get_repo_%s" % repo_name)) |
|
90 | "get_repo_%s" % repo_name)) | |
90 | return repo.scalar() |
|
91 | return repo.scalar() | |
91 |
|
92 | |||
|
93 | def get_all_user_repos(self, user): | |||
|
94 | """ | |||
|
95 | Get's all repositories that user have at least read access | |||
|
96 | ||||
|
97 | :param user: | |||
|
98 | :type user: | |||
|
99 | """ | |||
|
100 | from rhodecode.lib.auth import AuthUser | |||
|
101 | user = self._get_user(user) | |||
|
102 | repos = AuthUser(user_id=user.user_id).permissions['repositories'] | |||
|
103 | access_check = lambda r: r[1] in ['repository.read', | |||
|
104 | 'repository.write', | |||
|
105 | 'repository.admin'] | |||
|
106 | repos = [x[0] for x in filter(access_check, repos.items())] | |||
|
107 | return Repository.query().filter(Repository.repo_name.in_(repos)) | |||
|
108 | ||||
92 | def get_users_js(self): |
|
109 | def get_users_js(self): | |
93 | users = self.sa.query(User).filter(User.active == True).all() |
|
110 | users = self.sa.query(User).filter(User.active == True).all() | |
94 | return json.dumps([ |
|
111 | return json.dumps([ | |
@@ -113,6 +130,95 b' class RepoModel(BaseModel):' | |||||
113 | } for gr in users_groups] |
|
130 | } for gr in users_groups] | |
114 | ) |
|
131 | ) | |
115 |
|
132 | |||
|
133 | @classmethod | |||
|
134 | def _render_datatable(cls, tmpl, *args, **kwargs): | |||
|
135 | import rhodecode | |||
|
136 | from pylons import tmpl_context as c | |||
|
137 | from pylons.i18n.translation import _ | |||
|
138 | ||||
|
139 | _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup | |||
|
140 | template = _tmpl_lookup.get_template('data_table/_dt_elements.html') | |||
|
141 | ||||
|
142 | tmpl = template.get_def(tmpl) | |||
|
143 | kwargs.update(dict(_=_, h=h, c=c)) | |||
|
144 | return tmpl.render(*args, **kwargs) | |||
|
145 | ||||
|
146 | def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True): | |||
|
147 | _render = self._render_datatable | |||
|
148 | ||||
|
149 | def quick_menu(repo_name): | |||
|
150 | return _render('quick_menu', repo_name) | |||
|
151 | ||||
|
152 | def repo_lnk(name, rtype, private, fork_of): | |||
|
153 | return _render('repo_name', name, rtype, private, fork_of, | |||
|
154 | short_name=not admin, admin=False) | |||
|
155 | ||||
|
156 | def last_change(last_change): | |||
|
157 | return _render("last_change", last_change) | |||
|
158 | ||||
|
159 | def rss_lnk(repo_name): | |||
|
160 | return _render("rss", repo_name) | |||
|
161 | ||||
|
162 | def atom_lnk(repo_name): | |||
|
163 | return _render("atom", repo_name) | |||
|
164 | ||||
|
165 | def last_rev(repo_name, cs_cache): | |||
|
166 | return _render('revision', repo_name, cs_cache.get('revision'), | |||
|
167 | cs_cache.get('raw_id'), cs_cache.get('author'), | |||
|
168 | cs_cache.get('message')) | |||
|
169 | ||||
|
170 | def desc(desc): | |||
|
171 | from pylons import tmpl_context as c | |||
|
172 | if c.visual.stylify_metatags: | |||
|
173 | return h.urlify_text(h.desc_stylize(h.truncate(desc, 60))) | |||
|
174 | else: | |||
|
175 | return h.urlify_text(h.truncate(desc, 60)) | |||
|
176 | ||||
|
177 | def repo_actions(repo_name): | |||
|
178 | return _render('repo_actions', repo_name) | |||
|
179 | ||||
|
180 | def owner_actions(user_id, username): | |||
|
181 | return _render('user_name', user_id, username) | |||
|
182 | ||||
|
183 | repos_data = [] | |||
|
184 | for repo in repos_list: | |||
|
185 | if perm_check: | |||
|
186 | # check permission at this level | |||
|
187 | if not HasRepoPermissionAny( | |||
|
188 | 'repository.read', 'repository.write', 'repository.admin' | |||
|
189 | )(repo.repo_name, 'get_repos_as_dict check'): | |||
|
190 | continue | |||
|
191 | cs_cache = repo.changeset_cache | |||
|
192 | row = { | |||
|
193 | "menu": quick_menu(repo.repo_name), | |||
|
194 | "raw_name": repo.repo_name.lower(), | |||
|
195 | "name": repo_lnk(repo.repo_name, repo.repo_type, | |||
|
196 | repo.private, repo.fork), | |||
|
197 | "last_change": last_change(repo.last_db_change), | |||
|
198 | "last_changeset": last_rev(repo.repo_name, cs_cache), | |||
|
199 | "raw_tip": cs_cache.get('revision'), | |||
|
200 | "desc": desc(repo.description), | |||
|
201 | "owner": h.person(repo.user.username), | |||
|
202 | "rss": rss_lnk(repo.repo_name), | |||
|
203 | "atom": atom_lnk(repo.repo_name), | |||
|
204 | ||||
|
205 | } | |||
|
206 | if admin: | |||
|
207 | row.update({ | |||
|
208 | "action": repo_actions(repo.repo_name), | |||
|
209 | "owner": owner_actions(repo.user.user_id, | |||
|
210 | h.person(repo.user.username)) | |||
|
211 | }) | |||
|
212 | repos_data.append(row) | |||
|
213 | ||||
|
214 | return { | |||
|
215 | "totalRecords": len(repos_list), | |||
|
216 | "startIndex": 0, | |||
|
217 | "sort": "name", | |||
|
218 | "dir": "asc", | |||
|
219 | "records": repos_data | |||
|
220 | } | |||
|
221 | ||||
116 | def _get_defaults(self, repo_name): |
|
222 | def _get_defaults(self, repo_name): | |
117 | """ |
|
223 | """ | |
118 | Get's information about repository, and returns a dict for |
|
224 | Get's information about repository, and returns a dict for | |
@@ -339,9 +445,9 b' class RepoModel(BaseModel):' | |||||
339 | copy_fork_permissions = form_data.get('copy_permissions') |
|
445 | copy_fork_permissions = form_data.get('copy_permissions') | |
340 | fork_of = form_data.get('fork_parent_id') |
|
446 | fork_of = form_data.get('fork_parent_id') | |
341 |
|
447 | |||
342 | ##defaults |
|
448 | ## repo creation defaults, private and repo_type are filled in form | |
343 | defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) |
|
449 | defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True) | |
344 | enable_statistics = defs.get('repo_enable_statistic') |
|
450 | enable_statistics = defs.get('repo_enable_statistics') | |
345 | enable_locking = defs.get('repo_enable_locking') |
|
451 | enable_locking = defs.get('repo_enable_locking') | |
346 | enable_downloads = defs.get('repo_enable_downloads') |
|
452 | enable_downloads = defs.get('repo_enable_downloads') | |
347 |
|
453 |
@@ -273,7 +273,7 b' class ReposGroupModel(BaseModel):' | |||||
273 | self.sa.delete(repos_group) |
|
273 | self.sa.delete(repos_group) | |
274 | self.__delete_group(repos_group, force_delete) |
|
274 | self.__delete_group(repos_group, force_delete) | |
275 | except: |
|
275 | except: | |
276 |
log.e |
|
276 | log.error('Error removing repos_group %s' % repos_group) | |
277 | raise |
|
277 | raise | |
278 |
|
278 | |||
279 | def delete_permission(self, repos_group, obj, obj_type, recursive): |
|
279 | def delete_permission(self, repos_group, obj, obj_type, recursive): |
@@ -230,7 +230,7 b' class ScmModel(BaseModel):' | |||||
230 |
|
230 | |||
231 | # name need to be decomposed and put back together using the / |
|
231 | # name need to be decomposed and put back together using the / | |
232 | # since this is internal storage separator for rhodecode |
|
232 | # since this is internal storage separator for rhodecode | |
233 |
name = Repository. |
|
233 | name = Repository.normalize_repo_name(name) | |
234 |
|
234 | |||
235 | try: |
|
235 | try: | |
236 | if name in repos: |
|
236 | if name in repos: | |
@@ -292,6 +292,9 b' class ScmModel(BaseModel):' | |||||
292 | :param repo_name: this repo that should invalidation take place |
|
292 | :param repo_name: this repo that should invalidation take place | |
293 | """ |
|
293 | """ | |
294 | CacheInvalidation.set_invalidate(repo_name=repo_name) |
|
294 | CacheInvalidation.set_invalidate(repo_name=repo_name) | |
|
295 | repo = Repository.get_by_repo_name(repo_name) | |||
|
296 | if repo: | |||
|
297 | repo.update_changeset_cache() | |||
295 |
|
298 | |||
296 | def toggle_following_repo(self, follow_repo_id, user_id): |
|
299 | def toggle_following_repo(self, follow_repo_id, user_id): | |
297 |
|
300 |
@@ -27,7 +27,6 b' import logging' | |||||
27 | import traceback |
|
27 | import traceback | |
28 | import itertools |
|
28 | import itertools | |
29 | import collections |
|
29 | import collections | |
30 | import functools |
|
|||
31 | from pylons import url |
|
30 | from pylons import url | |
32 | from pylons.i18n.translation import _ |
|
31 | from pylons.i18n.translation import _ | |
33 |
|
32 | |||
@@ -40,7 +39,7 b' from rhodecode.model import BaseModel' | |||||
40 | from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ |
|
39 | from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ | |
41 | UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ |
|
40 | UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ | |
42 | Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \ |
|
41 | Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \ | |
43 | UserEmailMap |
|
42 | UserEmailMap, UserIpMap | |
44 | from rhodecode.lib.exceptions import DefaultUserException, \ |
|
43 | from rhodecode.lib.exceptions import DefaultUserException, \ | |
45 | UserOwnsReposException |
|
44 | UserOwnsReposException | |
46 |
|
45 | |||
@@ -294,30 +293,6 b' class UserModel(BaseModel):' | |||||
294 | log.error(traceback.format_exc()) |
|
293 | log.error(traceback.format_exc()) | |
295 | raise |
|
294 | raise | |
296 |
|
295 | |||
297 | def update_my_account(self, user_id, form_data): |
|
|||
298 | from rhodecode.lib.auth import get_crypt_password |
|
|||
299 | try: |
|
|||
300 | user = self.get(user_id, cache=False) |
|
|||
301 | if user.username == 'default': |
|
|||
302 | raise DefaultUserException( |
|
|||
303 | _("You can't Edit this user since it's" |
|
|||
304 | " crucial for entire application") |
|
|||
305 | ) |
|
|||
306 | for k, v in form_data.items(): |
|
|||
307 | if k == 'new_password' and v: |
|
|||
308 | user.password = get_crypt_password(v) |
|
|||
309 | user.api_key = generate_api_key(user.username) |
|
|||
310 | else: |
|
|||
311 | if k == 'firstname': |
|
|||
312 | k = 'name' |
|
|||
313 | if k not in ['admin', 'active']: |
|
|||
314 | setattr(user, k, v) |
|
|||
315 |
|
||||
316 | self.sa.add(user) |
|
|||
317 | except: |
|
|||
318 | log.error(traceback.format_exc()) |
|
|||
319 | raise |
|
|||
320 |
|
||||
321 | def delete(self, user): |
|
296 | def delete(self, user): | |
322 | user = self._get_user(user) |
|
297 | user = self._get_user(user) | |
323 |
|
298 | |||
@@ -705,3 +680,33 b' class UserModel(BaseModel):' | |||||
705 | obj = UserEmailMap.query().get(email_id) |
|
680 | obj = UserEmailMap.query().get(email_id) | |
706 | if obj: |
|
681 | if obj: | |
707 | self.sa.delete(obj) |
|
682 | self.sa.delete(obj) | |
|
683 | ||||
|
684 | def add_extra_ip(self, user, ip): | |||
|
685 | """ | |||
|
686 | Adds ip address to UserIpMap | |||
|
687 | ||||
|
688 | :param user: | |||
|
689 | :param ip: | |||
|
690 | """ | |||
|
691 | from rhodecode.model import forms | |||
|
692 | form = forms.UserExtraIpForm()() | |||
|
693 | data = form.to_python(dict(ip=ip)) | |||
|
694 | user = self._get_user(user) | |||
|
695 | ||||
|
696 | obj = UserIpMap() | |||
|
697 | obj.user = user | |||
|
698 | obj.ip_addr = data['ip'] | |||
|
699 | self.sa.add(obj) | |||
|
700 | return obj | |||
|
701 | ||||
|
702 | def delete_extra_ip(self, user, ip_id): | |||
|
703 | """ | |||
|
704 | Removes ip address from UserIpMap | |||
|
705 | ||||
|
706 | :param user: | |||
|
707 | :param ip_id: | |||
|
708 | """ | |||
|
709 | user = self._get_user(user) | |||
|
710 | obj = UserIpMap.query().get(ip_id) | |||
|
711 | if obj: | |||
|
712 | self.sa.delete(obj) |
@@ -11,7 +11,7 b' from webhelpers.pylonslib.secure_form im' | |||||
11 |
|
11 | |||
12 | from formencode.validators import ( |
|
12 | from formencode.validators import ( | |
13 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, |
|
13 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, | |
14 | NotEmpty |
|
14 | NotEmpty, IPAddress, CIDR | |
15 | ) |
|
15 | ) | |
16 | from rhodecode.lib.compat import OrderedSet |
|
16 | from rhodecode.lib.compat import OrderedSet | |
17 | from rhodecode.lib.utils import repo_name_slug |
|
17 | from rhodecode.lib.utils import repo_name_slug | |
@@ -23,7 +23,7 b' from rhodecode.lib.auth import HasReposG' | |||||
23 |
|
23 | |||
24 | # silence warnings and pylint |
|
24 | # silence warnings and pylint | |
25 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \ |
|
25 | UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \ | |
26 | NotEmpty |
|
26 | NotEmpty, IPAddress, CIDR | |
27 |
|
27 | |||
28 | log = logging.getLogger(__name__) |
|
28 | log = logging.getLogger(__name__) | |
29 |
|
29 | |||
@@ -566,7 +566,7 b" def ValidPerms(type_='repo'):" | |||||
566 | def ValidSettings(): |
|
566 | def ValidSettings(): | |
567 | class _validator(formencode.validators.FancyValidator): |
|
567 | class _validator(formencode.validators.FancyValidator): | |
568 | def _to_python(self, value, state): |
|
568 | def _to_python(self, value, state): | |
569 |
# settings form for users that are not admin |
|
569 | # settings form for users that are not admin | |
570 | # can't edit certain parameters, it's extra backup if they mangle |
|
570 | # can't edit certain parameters, it's extra backup if they mangle | |
571 | # with forms |
|
571 | # with forms | |
572 |
|
572 | |||
@@ -706,3 +706,40 b' def NotReviewedRevisions(repo_id):' | |||||
706 | ) |
|
706 | ) | |
707 |
|
707 | |||
708 | return _validator |
|
708 | return _validator | |
|
709 | ||||
|
710 | ||||
|
711 | def ValidIp(): | |||
|
712 | class _validator(CIDR): | |||
|
713 | messages = dict( | |||
|
714 | badFormat=_('Please enter a valid IP address (a.b.c.d)'), | |||
|
715 | illegalOctets=_('The octets must be within the range of 0-255' | |||
|
716 | ' (not %(octet)r)'), | |||
|
717 | illegalBits=_('The network size (bits) must be within the range' | |||
|
718 | ' of 0-32 (not %(bits)r)')) | |||
|
719 | ||||
|
720 | def validate_python(self, value, state): | |||
|
721 | try: | |||
|
722 | # Split into octets and bits | |||
|
723 | if '/' in value: # a.b.c.d/e | |||
|
724 | addr, bits = value.split('/') | |||
|
725 | else: # a.b.c.d | |||
|
726 | addr, bits = value, 32 | |||
|
727 | # Use IPAddress validator to validate the IP part | |||
|
728 | IPAddress.validate_python(self, addr, state) | |||
|
729 | # Bits (netmask) correct? | |||
|
730 | if not 0 <= int(bits) <= 32: | |||
|
731 | raise formencode.Invalid( | |||
|
732 | self.message('illegalBits', state, bits=bits), | |||
|
733 | value, state) | |||
|
734 | # Splitting faild: wrong syntax | |||
|
735 | except ValueError: | |||
|
736 | raise formencode.Invalid(self.message('badFormat', state), | |||
|
737 | value, state) | |||
|
738 | ||||
|
739 | def to_python(self, value, state): | |||
|
740 | v = super(_validator, self).to_python(value, state) | |||
|
741 | #if IP doesn't end with a mask, add /32 | |||
|
742 | if '/' not in value: | |||
|
743 | v += '/32' | |||
|
744 | return v | |||
|
745 | return _validator |
@@ -2002,7 +2002,6 b' a.metatag[tag="license"]:hover {' | |||||
2002 | } |
|
2002 | } | |
2003 |
|
2003 | |||
2004 | #login div.title { |
|
2004 | #login div.title { | |
2005 | width: 420px; |
|
|||
2006 | clear: both; |
|
2005 | clear: both; | |
2007 | overflow: hidden; |
|
2006 | overflow: hidden; | |
2008 | position: relative; |
|
2007 | position: relative; | |
@@ -2021,7 +2020,6 b' a.metatag[tag="license"]:hover {' | |||||
2021 | } |
|
2020 | } | |
2022 |
|
2021 | |||
2023 | #login div.inner { |
|
2022 | #login div.inner { | |
2024 | width: 380px; |
|
|||
2025 | background: #FFF url("../images/login.png") no-repeat top left; |
|
2023 | background: #FFF url("../images/login.png") no-repeat top left; | |
2026 | border-top: none; |
|
2024 | border-top: none; | |
2027 | border-bottom: none; |
|
2025 | border-bottom: none; | |
@@ -2038,7 +2036,6 b' a.metatag[tag="license"]:hover {' | |||||
2038 | } |
|
2036 | } | |
2039 |
|
2037 | |||
2040 | #login div.form div.fields div.field div.input input { |
|
2038 | #login div.form div.fields div.field div.input input { | |
2041 | width: 176px; |
|
|||
2042 | background: #FFF; |
|
2039 | background: #FFF; | |
2043 | border-top: 1px solid #b3b3b3; |
|
2040 | border-top: 1px solid #b3b3b3; | |
2044 | border-left: 1px solid #b3b3b3; |
|
2041 | border-left: 1px solid #b3b3b3; | |
@@ -2781,7 +2778,9 b' h3.files_location {' | |||||
2781 | margin: 0px 2px; |
|
2778 | margin: 0px 2px; | |
2782 | } |
|
2779 | } | |
2783 |
|
2780 | |||
2784 |
.right .logtags .branchtag, |
|
2781 | .right .logtags .branchtag, | |
|
2782 | .logtags .branchtag, | |||
|
2783 | .spantag { | |||
2785 | padding: 1px 3px 1px 3px; |
|
2784 | padding: 1px 3px 1px 3px; | |
2786 | background-color: #bfbfbf; |
|
2785 | background-color: #bfbfbf; | |
2787 | font-size: 10px; |
|
2786 | font-size: 10px; | |
@@ -3238,7 +3237,7 b' table.code-browser .submodule-dir {' | |||||
3238 | } |
|
3237 | } | |
3239 |
|
3238 | |||
3240 | .edit_icon { |
|
3239 | .edit_icon { | |
3241 |
background: url("../images/icons/fo |
|
3240 | background: url("../images/icons/application_form_edit.png") no-repeat scroll 3px; | |
3242 | padding-left: 20px; |
|
3241 | padding-left: 20px; | |
3243 | padding-top: 0px; |
|
3242 | padding-top: 0px; | |
3244 | text-align: left; |
|
3243 | text-align: left; | |
@@ -4040,6 +4039,22 b' div#legend_container table td,div#legend' | |||||
4040 | float: left |
|
4039 | float: left | |
4041 | } |
|
4040 | } | |
4042 |
|
4041 | |||
|
4042 | .ips_wrap{ | |||
|
4043 | padding: 0px 20px; | |||
|
4044 | } | |||
|
4045 | ||||
|
4046 | .ips_wrap .ip_entry{ | |||
|
4047 | height: 30px; | |||
|
4048 | padding:0px 0px 0px 10px; | |||
|
4049 | } | |||
|
4050 | .ips_wrap .ip_entry .ip{ | |||
|
4051 | float: left | |||
|
4052 | } | |||
|
4053 | .ips_wrap .ip_entry .ip_action{ | |||
|
4054 | float: left | |||
|
4055 | } | |||
|
4056 | ||||
|
4057 | ||||
4043 | /*README STYLE*/ |
|
4058 | /*README STYLE*/ | |
4044 |
|
4059 | |||
4045 | div.readme { |
|
4060 | div.readme { |
@@ -334,7 +334,7 b' var show_changeset_tooltip = function(){' | |||||
334 | YUD.setAttribute(target, 'title',_TM['loading...']); |
|
334 | YUD.setAttribute(target, 'title',_TM['loading...']); | |
335 | YAHOO.yuitip.main.set_listeners(target); |
|
335 | YAHOO.yuitip.main.set_listeners(target); | |
336 | YAHOO.yuitip.main.show_yuitip(e, target); |
|
336 | YAHOO.yuitip.main.show_yuitip(e, target); | |
337 | ajaxGET('/changeset_info/{0}/{1}'.format(repo_name,rid), success) |
|
337 | ajaxGET(LAZY_CS_URL.replace('__NAME__',repo_name).replace('__REV__', rid), success) | |
338 | } |
|
338 | } | |
339 | }); |
|
339 | }); | |
340 | }; |
|
340 | }; | |
@@ -416,7 +416,6 b' YAHOO.yuitip.main = {' | |||||
416 | }, |
|
416 | }, | |
417 |
|
417 | |||
418 | init: function(){ |
|
418 | init: function(){ | |
419 | yt._tooltip = ''; |
|
|||
420 | yt.tipBox = yt.$('tip-box'); |
|
419 | yt.tipBox = yt.$('tip-box'); | |
421 | if(!yt.tipBox){ |
|
420 | if(!yt.tipBox){ | |
422 | yt.tipBox = document.createElement('div'); |
|
421 | yt.tipBox = document.createElement('div'); | |
@@ -457,7 +456,7 b' YAHOO.yuitip.main = {' | |||||
457 |
|
456 | |||
458 | if(yt.tipText !== ''){ |
|
457 | if(yt.tipText !== ''){ | |
459 | // save org title |
|
458 | // save org title | |
460 | yt._tooltip = yt.tipText; |
|
459 | YUD.setAttribute(el, 'tt_title', yt.tipText); | |
461 | // reset title to not show org tooltips |
|
460 | // reset title to not show org tooltips | |
462 | YUD.setAttribute(el, 'title', ''); |
|
461 | YUD.setAttribute(el, 'title', ''); | |
463 |
|
462 | |||
@@ -495,7 +494,7 b' YAHOO.yuitip.main = {' | |||||
495 | } else { |
|
494 | } else { | |
496 | YUD.setStyle(yt.tipBox, 'display', 'none'); |
|
495 | YUD.setStyle(yt.tipBox, 'display', 'none'); | |
497 | } |
|
496 | } | |
498 |
YUD.setAttribute(el,'title', |
|
497 | YUD.setAttribute(el,'title', YUD.getAttribute(el, 'tt_title')); | |
499 | } |
|
498 | } | |
500 | } |
|
499 | } | |
501 |
|
500 |
@@ -53,4 +53,3 b" YUE.on('filter_form','submit',function(e" | |||||
53 | fix_j_filter_width(YUD.get('j_filter').value.length); |
|
53 | fix_j_filter_width(YUD.get('j_filter').value.length); | |
54 | </script> |
|
54 | </script> | |
55 | </%def> |
|
55 | </%def> | |
56 |
|
@@ -16,7 +16,7 b'' | |||||
16 | ${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))} |
|
16 | ${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))} | |
17 | %else: |
|
17 | %else: | |
18 | ${l.username} |
|
18 | ${l.username} | |
19 |
%endif |
|
19 | %endif | |
20 | </td> |
|
20 | </td> | |
21 | <td>${h.action_parser(l)[0]()} |
|
21 | <td>${h.action_parser(l)[0]()} | |
22 | <div class="journal_action_params"> |
|
22 | <div class="journal_action_params"> |
@@ -16,7 +16,7 b'' | |||||
16 | </%def> |
|
16 | </%def> | |
17 |
|
17 | |||
18 | <%def name="main()"> |
|
18 | <%def name="main()"> | |
19 | <div class="box"> |
|
19 | <div class="box box-left"> | |
20 | <!-- box / title --> |
|
20 | <!-- box / title --> | |
21 | <div class="title"> |
|
21 | <div class="title"> | |
22 | ${self.breadcrumbs()} |
|
22 | ${self.breadcrumbs()} | |
@@ -89,10 +89,127 b'' | |||||
89 | </div> |
|
89 | </div> | |
90 | </div> |
|
90 | </div> | |
91 | <div class="buttons"> |
|
91 | <div class="buttons"> | |
92 |
|
|
92 | ${h.submit('save',_('Save'),class_="ui-btn large")} | |
|
93 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} | |||
93 | </div> |
|
94 | </div> | |
94 | </div> |
|
95 | </div> | |
95 | </div> |
|
96 | </div> | |
96 | ${h.end_form()} |
|
97 | ${h.end_form()} | |
97 | </div> |
|
98 | </div> | |
|
99 | ||||
|
100 | <div style="min-height:780px" class="box box-right"> | |||
|
101 | <!-- box / title --> | |||
|
102 | <div class="title"> | |||
|
103 | <h5>${_('Default User Permissions')}</h5> | |||
|
104 | </div> | |||
|
105 | ||||
|
106 | ## permissions overview | |||
|
107 | <div id="perms" class="table"> | |||
|
108 | %for section in sorted(c.perm_user.permissions.keys()): | |||
|
109 | <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div> | |||
|
110 | %if not c.perm_user.permissions[section]: | |||
|
111 | <span class="empty_data">${_('Nothing here yet')}</span> | |||
|
112 | %else: | |||
|
113 | <div id='tbl_list_wrap_${section}' class="yui-skin-sam"> | |||
|
114 | <table id="tbl_list_${section}"> | |||
|
115 | <thead> | |||
|
116 | <tr> | |||
|
117 | <th class="left">${_('Name')}</th> | |||
|
118 | <th class="left">${_('Permission')}</th> | |||
|
119 | <th class="left">${_('Edit Permission')}</th> | |||
|
120 | </thead> | |||
|
121 | <tbody> | |||
|
122 | %for k in c.perm_user.permissions[section]: | |||
|
123 | <% | |||
|
124 | if section != 'global': | |||
|
125 | section_perm = c.perm_user.permissions[section].get(k) | |||
|
126 | _perm = section_perm.split('.')[-1] | |||
|
127 | else: | |||
|
128 | _perm = section_perm = None | |||
|
129 | %> | |||
|
130 | <tr> | |||
|
131 | <td> | |||
|
132 | %if section == 'repositories': | |||
|
133 | <a href="${h.url('summary_home',repo_name=k)}">${k}</a> | |||
|
134 | %elif section == 'repositories_groups': | |||
|
135 | <a href="${h.url('repos_group_home',group_name=k)}">${k}</a> | |||
|
136 | %else: | |||
|
137 | ${h.get_permission_name(k)} | |||
|
138 | %endif | |||
|
139 | </td> | |||
|
140 | <td> | |||
|
141 | %if section == 'global': | |||
|
142 | ${h.bool2icon(k.split('.')[-1] != 'none')} | |||
|
143 | %else: | |||
|
144 | <span class="perm_tag ${_perm}">${section_perm}</span> | |||
|
145 | %endif | |||
|
146 | </td> | |||
|
147 | <td> | |||
|
148 | %if section == 'repositories': | |||
|
149 | <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a> | |||
|
150 | %elif section == 'repositories_groups': | |||
|
151 | <a href="${h.url('edit_repos_group',id=k,anchor='permissions_manage')}">${_('edit')}</a> | |||
|
152 | %else: | |||
|
153 | -- | |||
|
154 | %endif | |||
|
155 | </td> | |||
|
156 | </tr> | |||
|
157 | %endfor | |||
|
158 | </tbody> | |||
|
159 | </table> | |||
|
160 | </div> | |||
|
161 | %endif | |||
|
162 | %endfor | |||
|
163 | </div> | |||
|
164 | </div> | |||
|
165 | <div class="box box-left" style="clear:left"> | |||
|
166 | <!-- box / title --> | |||
|
167 | <div class="title"> | |||
|
168 | <h5>${_('Allowed IP addresses')}</h5> | |||
|
169 | </div> | |||
|
170 | ||||
|
171 | <div class="ips_wrap"> | |||
|
172 | <table class="noborder"> | |||
|
173 | %if c.user_ip_map: | |||
|
174 | %for ip in c.user_ip_map: | |||
|
175 | <tr> | |||
|
176 | <td><div class="ip">${ip.ip_addr}</div></td> | |||
|
177 | <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td> | |||
|
178 | <td> | |||
|
179 | ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')} | |||
|
180 | ${h.hidden('del_ip',ip.ip_id)} | |||
|
181 | ${h.hidden('default_user', 'True')} | |||
|
182 | ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, | |||
|
183 | class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} | |||
|
184 | ${h.end_form()} | |||
|
185 | </td> | |||
|
186 | </tr> | |||
|
187 | %endfor | |||
|
188 | %else: | |||
|
189 | <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr> | |||
|
190 | %endif | |||
|
191 | </table> | |||
|
192 | </div> | |||
|
193 | ||||
|
194 | ${h.form(url('user_ips', id=c.user.user_id),method='put')} | |||
|
195 | <div class="form"> | |||
|
196 | <!-- fields --> | |||
|
197 | <div class="fields"> | |||
|
198 | <div class="field"> | |||
|
199 | <div class="label"> | |||
|
200 | <label for="new_ip">${_('New ip address')}:</label> | |||
|
201 | </div> | |||
|
202 | <div class="input"> | |||
|
203 | ${h.hidden('default_user', 'True')} | |||
|
204 | ${h.text('new_ip', class_='medium')} | |||
|
205 | </div> | |||
|
206 | </div> | |||
|
207 | <div class="buttons"> | |||
|
208 | ${h.submit('save',_('Add'),class_="ui-btn large")} | |||
|
209 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} | |||
|
210 | </div> | |||
|
211 | </div> | |||
|
212 | </div> | |||
|
213 | ${h.end_form()} | |||
|
214 | </div> | |||
98 | </%def> |
|
215 | </%def> |
@@ -40,6 +40,7 b'' | |||||
40 | {key:"raw_name"}, |
|
40 | {key:"raw_name"}, | |
41 | {key:"name"}, |
|
41 | {key:"name"}, | |
42 | {key:"desc"}, |
|
42 | {key:"desc"}, | |
|
43 | {key:"last_changeset"}, | |||
43 | {key:"owner"}, |
|
44 | {key:"owner"}, | |
44 | {key:"action"}, |
|
45 | {key:"action"}, | |
45 | ] |
|
46 | ] | |
@@ -70,6 +71,8 b'' | |||||
70 | {key:"name",label:"${_('Name')}",sortable:true, |
|
71 | {key:"name",label:"${_('Name')}",sortable:true, | |
71 | sortOptions: { sortFunction: nameSort }}, |
|
72 | sortOptions: { sortFunction: nameSort }}, | |
72 | {key:"desc",label:"${_('Description')}",sortable:true}, |
|
73 | {key:"desc",label:"${_('Description')}",sortable:true}, | |
|
74 | {key:"last_changeset",label:"${_('Tip')}",sortable:true, | |||
|
75 | sortOptions: { sortFunction: revisionSort }}, | |||
73 | {key:"owner",label:"${_('Owner')}",sortable:true}, |
|
76 | {key:"owner",label:"${_('Owner')}",sortable:true}, | |
74 | {key:"action",label:"${_('Action')}",sortable:false}, |
|
77 | {key:"action",label:"${_('Action')}",sortable:false}, | |
75 | ]; |
|
78 | ]; | |
@@ -77,7 +80,7 b'' | |||||
77 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ |
|
80 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ | |
78 | sortedBy:{key:"name",dir:"asc"}, |
|
81 | sortedBy:{key:"name",dir:"asc"}, | |
79 | paginator: new YAHOO.widget.Paginator({ |
|
82 | paginator: new YAHOO.widget.Paginator({ | |
80 |
rowsPerPage: |
|
83 | rowsPerPage: 25, | |
81 | alwaysVisible: false, |
|
84 | alwaysVisible: false, | |
82 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", |
|
85 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", | |
83 | pageLinks: 5, |
|
86 | pageLinks: 5, | |
@@ -111,7 +114,7 b'' | |||||
111 |
|
114 | |||
112 | // Reset sort |
|
115 | // Reset sort | |
113 | var state = myDataTable.getState(); |
|
116 | var state = myDataTable.getState(); | |
114 |
|
|
117 | state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC}; | |
115 |
|
118 | |||
116 | // Get filtered data |
|
119 | // Get filtered data | |
117 | myDataSource.sendRequest(YUD.get('q_filter').value,{ |
|
120 | myDataSource.sendRequest(YUD.get('q_filter').value,{ | |
@@ -123,7 +126,11 b'' | |||||
123 |
|
126 | |||
124 | }; |
|
127 | }; | |
125 | YUE.on('q_filter','click',function(){ |
|
128 | YUE.on('q_filter','click',function(){ | |
126 | YUD.get('q_filter').value = ''; |
|
129 | if(!YUD.hasClass('q_filter', 'loaded')){ | |
|
130 | YUD.get('q_filter').value = ''; | |||
|
131 | //TODO: load here full list later to do search within groups | |||
|
132 | YUD.addClass('q_filter', 'loaded'); | |||
|
133 | } | |||
127 | }); |
|
134 | }); | |
128 |
|
135 | |||
129 | YUE.on('q_filter','keyup',function (e) { |
|
136 | YUE.on('q_filter','keyup',function (e) { |
@@ -43,7 +43,11 b'' | |||||
43 | <label>${_('API key')}</label> ${c.user.api_key} |
|
43 | <label>${_('API key')}</label> ${c.user.api_key} | |
44 | </div> |
|
44 | </div> | |
45 | </div> |
|
45 | </div> | |
46 |
|
46 | <div class="field"> | ||
|
47 | <div class="label"> | |||
|
48 | <label>${_('Your IP')}</label> ${c.perm_user.ip_addr or "?"} | |||
|
49 | </div> | |||
|
50 | </div> | |||
47 | <div class="fields"> |
|
51 | <div class="fields"> | |
48 | <div class="field"> |
|
52 | <div class="field"> | |
49 | <div class="label"> |
|
53 | <div class="label"> | |
@@ -271,7 +275,7 b'' | |||||
271 | <div class="fields"> |
|
275 | <div class="fields"> | |
272 | <div class="field"> |
|
276 | <div class="field"> | |
273 | <div class="label"> |
|
277 | <div class="label"> | |
274 | <label for="email">${_('New email address')}:</label> |
|
278 | <label for="new_email">${_('New email address')}:</label> | |
275 | </div> |
|
279 | </div> | |
276 | <div class="input"> |
|
280 | <div class="input"> | |
277 | ${h.text('new_email', class_='medium')} |
|
281 | ${h.text('new_email', class_='medium')} | |
@@ -285,4 +289,52 b'' | |||||
285 | </div> |
|
289 | </div> | |
286 | ${h.end_form()} |
|
290 | ${h.end_form()} | |
287 | </div> |
|
291 | </div> | |
|
292 | <div class="box box-left" style="clear:left"> | |||
|
293 | <!-- box / title --> | |||
|
294 | <div class="title"> | |||
|
295 | <h5>${_('Allowed IP addresses')}</h5> | |||
|
296 | </div> | |||
|
297 | ||||
|
298 | <div class="ips_wrap"> | |||
|
299 | <table class="noborder"> | |||
|
300 | %if c.user_ip_map: | |||
|
301 | %for ip in c.user_ip_map: | |||
|
302 | <tr> | |||
|
303 | <td><div class="ip">${ip.ip_addr}</div></td> | |||
|
304 | <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td> | |||
|
305 | <td> | |||
|
306 | ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')} | |||
|
307 | ${h.hidden('del_ip',ip.ip_id)} | |||
|
308 | ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id, | |||
|
309 | class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")} | |||
|
310 | ${h.end_form()} | |||
|
311 | </td> | |||
|
312 | </tr> | |||
|
313 | %endfor | |||
|
314 | %else: | |||
|
315 | <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr> | |||
|
316 | %endif | |||
|
317 | </table> | |||
|
318 | </div> | |||
|
319 | ||||
|
320 | ${h.form(url('user_ips', id=c.user.user_id),method='put')} | |||
|
321 | <div class="form"> | |||
|
322 | <!-- fields --> | |||
|
323 | <div class="fields"> | |||
|
324 | <div class="field"> | |||
|
325 | <div class="label"> | |||
|
326 | <label for="new_ip">${_('New ip address')}:</label> | |||
|
327 | </div> | |||
|
328 | <div class="input"> | |||
|
329 | ${h.text('new_ip', class_='medium')} | |||
|
330 | </div> | |||
|
331 | </div> | |||
|
332 | <div class="buttons"> | |||
|
333 | ${h.submit('save',_('Add'),class_="ui-btn large")} | |||
|
334 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} | |||
|
335 | </div> | |||
|
336 | </div> | |||
|
337 | </div> | |||
|
338 | ${h.end_form()} | |||
|
339 | </div> | |||
288 | </%def> |
|
340 | </%def> |
@@ -48,7 +48,7 b'' | |||||
48 | </ul> |
|
48 | </ul> | |
49 | </div> |
|
49 | </div> | |
50 | <!-- end box / title --> |
|
50 | <!-- end box / title --> | |
51 | <div id="perms" class="table"> |
|
51 | <div id="perms_container" class="table"> | |
52 | %for section in sorted(c.rhodecode_user.permissions.keys()): |
|
52 | %for section in sorted(c.rhodecode_user.permissions.keys()): | |
53 | <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div> |
|
53 | <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div> | |
54 |
|
54 | |||
@@ -94,30 +94,26 b'' | |||||
94 | </div> |
|
94 | </div> | |
95 | %endfor |
|
95 | %endfor | |
96 | </div> |
|
96 | </div> | |
97 |
<div id="my |
|
97 | <div id="my_container" style="display:none"> | |
|
98 | <div class="table yui-skin-sam" id="repos_list_wrap"></div> | |||
|
99 | <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div> | |||
98 | </div> |
|
100 | </div> | |
99 |
<div id="pullrequests" class="table" style="display:none"> |
|
101 | <div id="pullrequests_container" class="table" style="display:none"> | |
|
102 | ## loaded via AJAX | |||
|
103 | ${_('Loading...')} | |||
|
104 | </div> | |||
100 | </div> |
|
105 | </div> | |
101 |
|
106 | |||
102 |
|
||||
103 |
|
||||
104 | <script type="text/javascript"> |
|
107 | <script type="text/javascript"> | |
105 | var filter_activate = function(){ |
|
|||
106 | var nodes = YUQ('#my tr td a.repo_name'); |
|
|||
107 | var func = function(node){ |
|
|||
108 | return node.parentNode.parentNode.parentNode.parentNode; |
|
|||
109 | } |
|
|||
110 | q_filter('q_filter',YUQ('#my tr td a.repo_name'),func); |
|
|||
111 | } |
|
|||
112 |
|
108 | |||
113 | var show_perms = function(e){ |
|
109 | var show_perms = function(e){ | |
114 | YUD.addClass('show_perms', 'current'); |
|
110 | YUD.addClass('show_perms', 'current'); | |
115 | YUD.removeClass('show_my','current'); |
|
111 | YUD.removeClass('show_my','current'); | |
116 | YUD.removeClass('show_pullrequests','current'); |
|
112 | YUD.removeClass('show_pullrequests','current'); | |
117 |
|
113 | |||
118 | YUD.setStyle('my','display','none'); |
|
114 | YUD.setStyle('my_container','display','none'); | |
119 | YUD.setStyle('pullrequests','display','none'); |
|
115 | YUD.setStyle('pullrequests_container','display','none'); | |
120 | YUD.setStyle('perms','display',''); |
|
116 | YUD.setStyle('perms_container','display',''); | |
121 | YUD.setStyle('q_filter','display','none'); |
|
117 | YUD.setStyle('q_filter','display','none'); | |
122 | } |
|
118 | } | |
123 | YUE.on('show_perms','click',function(e){ |
|
119 | YUE.on('show_perms','click',function(e){ | |
@@ -129,17 +125,14 b' var show_my = function(e){' | |||||
129 | YUD.removeClass('show_perms','current'); |
|
125 | YUD.removeClass('show_perms','current'); | |
130 | YUD.removeClass('show_pullrequests','current'); |
|
126 | YUD.removeClass('show_pullrequests','current'); | |
131 |
|
127 | |||
132 | YUD.setStyle('perms','display','none'); |
|
128 | YUD.setStyle('perms_container','display','none'); | |
133 | YUD.setStyle('pullrequests','display','none'); |
|
129 | YUD.setStyle('pullrequests_container','display','none'); | |
134 | YUD.setStyle('my','display',''); |
|
130 | YUD.setStyle('my_container','display',''); | |
135 | YUD.setStyle('q_filter','display',''); |
|
131 | YUD.setStyle('q_filter','display',''); | |
136 |
|
132 | if(!YUD.hasClass('show_my', 'loaded')){ | ||
137 |
|
133 | table_renderer(${c.data |n}); | ||
138 | var url = "${h.url('journal_my_repos')}"; |
|
134 | YUD.addClass('show_my', 'loaded'); | |
139 | ypjax(url, 'my', function(){ |
|
135 | } | |
140 | table_sort(); |
|
|||
141 | filter_activate(); |
|
|||
142 | }); |
|
|||
143 | } |
|
136 | } | |
144 | YUE.on('show_my','click',function(e){ |
|
137 | YUE.on('show_my','click',function(e){ | |
145 | show_my(e); |
|
138 | show_my(e); | |
@@ -150,13 +143,13 b' var show_pullrequests = function(e){' | |||||
150 | YUD.removeClass('show_my','current'); |
|
143 | YUD.removeClass('show_my','current'); | |
151 | YUD.removeClass('show_perms','current'); |
|
144 | YUD.removeClass('show_perms','current'); | |
152 |
|
145 | |||
153 | YUD.setStyle('my','display','none'); |
|
146 | YUD.setStyle('my_container','display','none'); | |
154 | YUD.setStyle('perms','display','none'); |
|
147 | YUD.setStyle('perms_container','display','none'); | |
155 | YUD.setStyle('pullrequests','display',''); |
|
148 | YUD.setStyle('pullrequests_container','display',''); | |
156 | YUD.setStyle('q_filter','display','none'); |
|
149 | YUD.setStyle('q_filter','display','none'); | |
157 |
|
150 | |||
158 | var url = "${h.url('admin_settings_my_pullrequests')}"; |
|
151 | var url = "${h.url('admin_settings_my_pullrequests')}"; | |
159 | ypjax(url, 'pullrequests'); |
|
152 | ypjax(url, 'pullrequests_container'); | |
160 | } |
|
153 | } | |
161 | YUE.on('show_pullrequests','click',function(e){ |
|
154 | YUE.on('show_pullrequests','click',function(e){ | |
162 | show_pullrequests(e) |
|
155 | show_pullrequests(e) | |
@@ -171,75 +164,115 b" var url = location.href.split('#');" | |||||
171 | if (url[1]) { |
|
164 | if (url[1]) { | |
172 | //We have a hash |
|
165 | //We have a hash | |
173 | var tabHash = url[1]; |
|
166 | var tabHash = url[1]; | |
174 |
tabs[tabHash] |
|
167 | var func = tabs[tabHash] | |
|
168 | if (func){ | |||
|
169 | func(); | |||
|
170 | } | |||
175 | } |
|
171 | } | |
176 |
|
172 | |||
177 | // main table sorting |
|
173 | function table_renderer(data){ | |
178 | var myColumnDefs = [ |
|
174 | var myDataSource = new YAHOO.util.DataSource(data); | |
179 | {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"}, |
|
175 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON; | |
180 | {key:"name",label:"${_('Name')}",sortable:true, |
|
176 | ||
181 | sortOptions: { sortFunction: nameSort }}, |
|
177 | myDataSource.responseSchema = { | |
182 | {key:"tip",label:"${_('Tip')}",sortable:true, |
|
178 | resultsList: "records", | |
183 | sortOptions: { sortFunction: revisionSort }}, |
|
179 | fields: [ | |
184 | {key:"action1",label:"",sortable:false}, |
|
180 | {key:"menu"}, | |
185 | {key:"action2",label:"",sortable:false}, |
|
181 | {key:"raw_name"}, | |
186 | ]; |
|
182 | {key:"name"}, | |
|
183 | {key:"last_changeset"}, | |||
|
184 | {key:"action"}, | |||
|
185 | ] | |||
|
186 | }; | |||
|
187 | myDataSource.doBeforeCallback = function(req,raw,res,cb) { | |||
|
188 | // This is the filter function | |||
|
189 | var data = res.results || [], | |||
|
190 | filtered = [], | |||
|
191 | i,l; | |||
|
192 | ||||
|
193 | if (req) { | |||
|
194 | req = req.toLowerCase(); | |||
|
195 | for (i = 0; i<data.length; i++) { | |||
|
196 | var pos = data[i].raw_name.toLowerCase().indexOf(req) | |||
|
197 | if (pos != -1) { | |||
|
198 | filtered.push(data[i]); | |||
|
199 | } | |||
|
200 | } | |||
|
201 | res.results = filtered; | |||
|
202 | } | |||
|
203 | return res; | |||
|
204 | } | |||
|
205 | ||||
|
206 | // main table sorting | |||
|
207 | var myColumnDefs = [ | |||
|
208 | {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"}, | |||
|
209 | {key:"name",label:"${_('Name')}",sortable:true, | |||
|
210 | sortOptions: { sortFunction: nameSort }}, | |||
|
211 | {key:"last_changeset",label:"${_('Tip')}",sortable:true, | |||
|
212 | sortOptions: { sortFunction: revisionSort }}, | |||
|
213 | {key:"action",label:"${_('Action')}",sortable:false}, | |||
|
214 | ]; | |||
187 |
|
215 | |||
188 | function table_sort(){ |
|
216 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ | |
189 | var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list")); |
|
217 | sortedBy:{key:"name",dir:"asc"}, | |
190 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; |
|
218 | paginator: new YAHOO.widget.Paginator({ | |
191 | myDataSource.responseSchema = { |
|
219 | rowsPerPage: 50, | |
192 | fields: [ |
|
220 | alwaysVisible: false, | |
193 | {key:"menu"}, |
|
221 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", | |
194 | {key:"name"}, |
|
222 | pageLinks: 5, | |
195 | {key:"tip"}, |
|
223 | containerClass: 'pagination-wh', | |
196 | {key:"action1"}, |
|
224 | currentPageClass: 'pager_curpage', | |
197 | {key:"action2"}, |
|
225 | pageLinkClass: 'pager_link', | |
198 | ] |
|
226 | nextPageLinkLabel: '>', | |
199 | }; |
|
227 | previousPageLinkLabel: '<', | |
200 | var trans_defs = { |
|
228 | firstPageLinkLabel: '<<', | |
201 | sortedBy:{key:"name",dir:"asc"}, |
|
229 | lastPageLinkLabel: '>>', | |
202 | MSG_SORTASC:"${_('Click to sort ascending')}", |
|
230 | containers:['user-paginator'] | |
203 | MSG_SORTDESC:"${_('Click to sort descending')}", |
|
231 | }), | |
204 | MSG_EMPTY:"${_('No records found.')}", |
|
232 | ||
205 | MSG_ERROR:"${_('Data error.')}", |
|
233 | MSG_SORTASC:"${_('Click to sort ascending')}", | |
206 |
MSG_ |
|
234 | MSG_SORTDESC:"${_('Click to sort descending')}", | |
207 | } |
|
235 | MSG_EMPTY:"${_('No records found.')}", | |
208 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,trans_defs); |
|
236 | MSG_ERROR:"${_('Data error.')}", | |
209 | myDataTable.subscribe('postRenderEvent',function(oArgs) { |
|
237 | MSG_LOADING:"${_('Loading...')}", | |
210 | tooltip_activate(); |
|
238 | } | |
211 | quick_repo_menu(); |
|
239 | ); | |
212 | filter_activate(); |
|
240 | myDataTable.subscribe('postRenderEvent',function(oArgs) { | |
213 | }); |
|
241 | tooltip_activate(); | |
|
242 | quick_repo_menu(); | |||
|
243 | }); | |||
|
244 | ||||
|
245 | var filterTimeout = null; | |||
214 |
|
246 | |||
215 | var permsColumnDefs = [ |
|
247 | updateFilter = function() { | |
216 | {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: permNameSort }}, |
|
248 | // Reset timeout | |
217 | {key:"perm",label:"${_('Permission')}",sortable:false,}, |
|
249 | filterTimeout = null; | |
218 | ]; |
|
|||
219 |
|
250 | |||
220 | // perms repos table |
|
251 | // Reset sort | |
221 | var myDataSource2 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories")); |
|
252 | var state = myDataTable.getState(); | |
222 | myDataSource2.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; |
|
253 | state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC}; | |
223 | myDataSource2.responseSchema = { |
|
|||
224 | fields: [ |
|
|||
225 | {key:"name"}, |
|
|||
226 | {key:"perm"}, |
|
|||
227 | ] |
|
|||
228 | }; |
|
|||
229 |
|
254 | |||
230 | new YAHOO.widget.DataTable("tbl_list_wrap_repositories", permsColumnDefs, myDataSource2, trans_defs); |
|
255 | // Get filtered data | |
|
256 | myDataSource.sendRequest(YUD.get('q_filter').value,{ | |||
|
257 | success : myDataTable.onDataReturnInitializeTable, | |||
|
258 | failure : myDataTable.onDataReturnInitializeTable, | |||
|
259 | scope : myDataTable, | |||
|
260 | argument: state | |||
|
261 | }); | |||
231 |
|
262 | |||
232 | //perms groups table |
|
263 | }; | |
233 | var myDataSource3 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories_groups")); |
|
264 | YUE.on('q_filter','click',function(){ | |
234 | myDataSource3.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; |
|
265 | if(!YUD.hasClass('q_filter', 'loaded')){ | |
235 | myDataSource3.responseSchema = { |
|
266 | YUD.get('q_filter').value = ''; | |
236 | fields: [ |
|
267 | //TODO: load here full list later to do search within groups | |
237 | {key:"name"}, |
|
268 | YUD.addClass('q_filter', 'loaded'); | |
238 | {key:"perm"}, |
|
269 | } | |
239 | ] |
|
270 | }); | |
240 | }; |
|
|||
241 |
|
271 | |||
242 | new YAHOO.widget.DataTable("tbl_list_wrap_repositories_groups", permsColumnDefs, myDataSource3, trans_defs); |
|
272 | YUE.on('q_filter','keyup',function (e) { | |
243 | } |
|
273 | clearTimeout(filterTimeout); | |
|
274 | filterTimeout = setTimeout(updateFilter,600); | |||
|
275 | }); | |||
|
276 | } | |||
244 | </script> |
|
277 | </script> | |
245 | </%def> |
|
278 | </%def> |
@@ -26,7 +26,11 b'' | |||||
26 | <label for="username">${_('Username')}:</label> |
|
26 | <label for="username">${_('Username')}:</label> | |
27 | </div> |
|
27 | </div> | |
28 | <div class="input"> |
|
28 | <div class="input"> | |
29 | ${h.text('username',class_="medium")} |
|
29 | %if c.ldap_dn: | |
|
30 | ${h.text('username',class_='medium disabled', readonly="readonly")} | |||
|
31 | %else: | |||
|
32 | ${h.text('username',class_='medium')} | |||
|
33 | %endif: | |||
30 | </div> |
|
34 | </div> | |
31 | </div> |
|
35 | </div> | |
32 |
|
36 |
@@ -1,46 +0,0 b'' | |||||
1 | <div id='repos_list_wrap' class="yui-skin-sam"> |
|
|||
2 | <table id="repos_list"> |
|
|||
3 | <thead> |
|
|||
4 | <tr> |
|
|||
5 | <th></th> |
|
|||
6 | <th class="left">${_('Name')}</th> |
|
|||
7 | <th class="left">${_('Revision')}</th> |
|
|||
8 | <th class="left">${_('Action')}</th> |
|
|||
9 | <th class="left">${_('Action')}</th> |
|
|||
10 | </thead> |
|
|||
11 | <tbody> |
|
|||
12 | <%namespace name="dt" file="/data_table/_dt_elements.html"/> |
|
|||
13 | %if c.user_repos: |
|
|||
14 | %for repo in c.user_repos: |
|
|||
15 | <tr> |
|
|||
16 | ##QUICK MENU |
|
|||
17 | <td class="quick_repo_menu"> |
|
|||
18 | ${dt.quick_menu(repo['name'])} |
|
|||
19 | </td> |
|
|||
20 | ##REPO NAME AND ICONS |
|
|||
21 | <td class="reponame"> |
|
|||
22 | ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']))} |
|
|||
23 | </td> |
|
|||
24 | ##LAST REVISION |
|
|||
25 | <td> |
|
|||
26 | ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])} |
|
|||
27 | </td> |
|
|||
28 | <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td> |
|
|||
29 | <td> |
|
|||
30 | ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')} |
|
|||
31 | ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")} |
|
|||
32 | ${h.end_form()} |
|
|||
33 | </td> |
|
|||
34 | </tr> |
|
|||
35 | %endfor |
|
|||
36 | %else: |
|
|||
37 | <div style="padding:5px 0px 10px 0px;"> |
|
|||
38 | ${_('No repositories yet')} |
|
|||
39 | %if h.HasPermissionAny('hg.admin','hg.create.repository')(): |
|
|||
40 | ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")} |
|
|||
41 | %endif |
|
|||
42 | </div> |
|
|||
43 | %endif |
|
|||
44 | </tbody> |
|
|||
45 | </table> |
|
|||
46 | </div> |
|
@@ -54,6 +54,7 b'' | |||||
54 | }; |
|
54 | }; | |
55 | var _TM = TRANSLATION_MAP; |
|
55 | var _TM = TRANSLATION_MAP; | |
56 | var TOGGLE_FOLLOW_URL = "${h.url('toggle_following')}"; |
|
56 | var TOGGLE_FOLLOW_URL = "${h.url('toggle_following')}"; | |
|
57 | var LAZY_CS_URL = "${h.url('changeset_info', repo_name='__NAME__', revision='__REV__')}" | |||
57 | </script> |
|
58 | </script> | |
58 | <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script> |
|
59 | <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.rhodecode_version)}"></script> | |
59 | <!--[if lt IE 9]> |
|
60 | <!--[if lt IE 9]> | |
@@ -74,7 +75,7 b'' | |||||
74 | return false; |
|
75 | return false; | |
75 | } |
|
76 | } | |
76 | })(window); |
|
77 | })(window); | |
77 |
|
78 | |||
78 | YUE.onDOMReady(function(){ |
|
79 | YUE.onDOMReady(function(){ | |
79 | tooltip_activate(); |
|
80 | tooltip_activate(); | |
80 | show_more_event(); |
|
81 | show_more_event(); | |
@@ -83,7 +84,7 b'' | |||||
83 | YUE.on('quick_login_link','click',function(e){ |
|
84 | YUE.on('quick_login_link','click',function(e){ | |
84 | // make sure we don't redirect |
|
85 | // make sure we don't redirect | |
85 | YUE.preventDefault(e); |
|
86 | YUE.preventDefault(e); | |
86 |
|
87 | |||
87 | if(YUD.hasClass('quick_login_link','enabled')){ |
|
88 | if(YUD.hasClass('quick_login_link','enabled')){ | |
88 | YUD.setStyle('quick_login','display','none'); |
|
89 | YUD.setStyle('quick_login','display','none'); | |
89 | YUD.removeClass('quick_login_link','enabled'); |
|
90 | YUD.removeClass('quick_login_link','enabled'); |
@@ -149,6 +149,7 b'' | |||||
149 | //ranges |
|
149 | //ranges | |
150 | var checkboxes = YUD.getElementsByClassName('changeset_range'); |
|
150 | var checkboxes = YUD.getElementsByClassName('changeset_range'); | |
151 | var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}"; |
|
151 | var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}"; | |
|
152 | var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}"; | |||
152 | YUE.on(checkboxes,'click',function(e){ |
|
153 | YUE.on(checkboxes,'click',function(e){ | |
153 | var clicked_cb = e.currentTarget; |
|
154 | var clicked_cb = e.currentTarget; | |
154 | var checked_checkboxes = []; |
|
155 | var checked_checkboxes = []; | |
@@ -203,7 +204,7 b'' | |||||
203 | YUD.setStyle('rev_range_container','display',''); |
|
204 | YUD.setStyle('rev_range_container','display',''); | |
204 | YUD.setStyle('rev_range_clear','display',''); |
|
205 | YUD.setStyle('rev_range_clear','display',''); | |
205 |
|
206 | |||
206 |
YUD.get('open_new_pr').href + |
|
207 | YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end); | |
207 |
|
208 | |||
208 | } |
|
209 | } | |
209 | else{ |
|
210 | else{ |
@@ -40,7 +40,7 b'' | |||||
40 | %endfor |
|
40 | %endfor | |
41 | %else: |
|
41 | %else: | |
42 | <span>${_('No parents')}</span> |
|
42 | <span>${_('No parents')}</span> | |
43 |
%endif |
|
43 | %endif | |
44 | </div> |
|
44 | </div> | |
45 | <div class="children"> |
|
45 | <div class="children"> | |
46 | %if c.changeset.children: |
|
46 | %if c.changeset.children: | |
@@ -50,10 +50,10 b'' | |||||
50 | %endfor |
|
50 | %endfor | |
51 | %else: |
|
51 | %else: | |
52 | <span>${_('No children')}</span> |
|
52 | <span>${_('No children')}</span> | |
53 |
%endif |
|
53 | %endif | |
54 |
</div> |
|
54 | </div> | |
55 | <div class="code-header banner"> |
|
55 | <div class="code-header banner"> | |
56 |
|
56 | |||
57 | <div class="hash"> |
|
57 | <div class="hash"> | |
58 | r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} |
|
58 | r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} | |
59 | </div> |
|
59 | </div> | |
@@ -74,7 +74,7 b'' | |||||
74 | ${c.context_url(request.GET)} |
|
74 | ${c.context_url(request.GET)} | |
75 | </div> |
|
75 | </div> | |
76 | <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div> |
|
76 | <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div> | |
77 |
</div> |
|
77 | </div> | |
78 | </div> |
|
78 | </div> | |
79 | <div id="changeset_content"> |
|
79 | <div id="changeset_content"> | |
80 | <div class="container"> |
|
80 | <div class="container"> |
@@ -19,6 +19,11 b'' | |||||
19 | <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">›</span></div> |
|
19 | <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">›</span></div> | |
20 | <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change[0].status_lbl}</div> |
|
20 | <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change[0].status_lbl}</div> | |
21 | <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change[0].status))}" /></div> |
|
21 | <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change[0].status))}" /></div> | |
|
22 | <div style="float:left;padding:3px 0px 0px 5px"> <span class=""> | |||
|
23 | %if co.pull_request: | |||
|
24 | <a href="${h.url('pullrequest_show',repo_name=co.pull_request.other_repo.repo_name,pull_request_id=co.pull_request.pull_request_id)}">${_('Status from pull request %s') % co.pull_request.pull_request_id}</a> | |||
|
25 | %endif | |||
|
26 | </span> </div> | |||
22 | </div> |
|
27 | </div> | |
23 | %endif |
|
28 | %endif | |
24 | %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id: |
|
29 | %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id: | |
@@ -130,7 +135,7 b'' | |||||
130 | <div id="status_block_container" class="status-block" style="display:none"> |
|
135 | <div id="status_block_container" class="status-block" style="display:none"> | |
131 | %for status,lbl in c.changeset_statuses: |
|
136 | %for status,lbl in c.changeset_statuses: | |
132 | <div class=""> |
|
137 | <div class=""> | |
133 |
<img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}"> |
|
138 | <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}"> | |
134 | <label for="${status}">${lbl}</label> |
|
139 | <label for="${status}">${lbl}</label> | |
135 | </div> |
|
140 | </div> | |
136 | %endfor |
|
141 | %endfor |
@@ -2,7 +2,7 b'' | |||||
2 | <%inherit file="/base/base.html"/> |
|
2 | <%inherit file="/base/base.html"/> | |
3 |
|
3 | |||
4 | <%def name="title()"> |
|
4 | <%def name="title()"> | |
5 |
${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} - |
|
5 | ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} | |
6 | </%def> |
|
6 | </%def> | |
7 |
|
7 | |||
8 | <%def name="breadcrumbs_links()"> |
|
8 | <%def name="breadcrumbs_links()"> | |
@@ -28,7 +28,7 b'' | |||||
28 | <div class="code-header cv"> |
|
28 | <div class="code-header cv"> | |
29 | <h3 class="code-header-title">${_('Compare View')}</h3> |
|
29 | <h3 class="code-header-title">${_('Compare View')}</h3> | |
30 | <div> |
|
30 | <div> | |
31 |
${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} - |
|
31 | ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a> | |
32 | </div> |
|
32 | </div> | |
33 | </div> |
|
33 | </div> | |
34 | </div> |
|
34 | </div> |
@@ -2,12 +2,6 b'' | |||||
2 | ## usage: |
|
2 | ## usage: | |
3 | ## <%namespace name="dt" file="/data_table/_dt_elements.html"/> |
|
3 | ## <%namespace name="dt" file="/data_table/_dt_elements.html"/> | |
4 |
|
4 | |||
5 | <%def name="repo_actions(repo_name)"> |
|
|||
6 | ${h.form(h.url('repo', repo_name=repo_name),method='delete')} |
|
|||
7 | ${h.submit('remove_%s' % repo_name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")} |
|
|||
8 | ${h.end_form()} |
|
|||
9 | </%def> |
|
|||
10 |
|
||||
11 | <%def name="quick_menu(repo_name)"> |
|
5 | <%def name="quick_menu(repo_name)"> | |
12 | <ul class="menu_items hidden"> |
|
6 | <ul class="menu_items hidden"> | |
13 | <li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li> |
|
7 | <li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li> | |
@@ -46,7 +40,7 b'' | |||||
46 | </ul> |
|
40 | </ul> | |
47 | </%def> |
|
41 | </%def> | |
48 |
|
42 | |||
49 |
<%def name="repo_name(name,rtype,private,fork_of,short_name=False, |
|
43 | <%def name="repo_name(name,rtype,private,fork_of,short_name=False,admin=False)"> | |
50 | <% |
|
44 | <% | |
51 | def get_name(name,short_name=short_name): |
|
45 | def get_name(name,short_name=short_name): | |
52 | if short_name: |
|
46 | if short_name: | |
@@ -116,6 +110,21 b'' | |||||
116 | <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(email, size)}"/> </div> |
|
110 | <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(email, size)}"/> </div> | |
117 | </%def> |
|
111 | </%def> | |
118 |
|
112 | |||
|
113 | <%def name="repo_actions(repo_name)"> | |||
|
114 | <div> | |||
|
115 | <div style="float:left"> | |||
|
116 | <a href="${h.url('repo_settings_home',repo_name=repo_name)}" title="${_('edit')}"> | |||
|
117 | ${h.submit('edit_%s' % repo_name,_('edit'),class_="edit_icon action_button")} | |||
|
118 | </a> | |||
|
119 | </div> | |||
|
120 | <div style="float:left"> | |||
|
121 | ${h.form(h.url('repo', repo_name=repo_name),method='delete')} | |||
|
122 | ${h.submit('remove_%s' % repo_name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")} | |||
|
123 | ${h.end_form()} | |||
|
124 | </div> | |||
|
125 | </div> | |||
|
126 | </%def> | |||
|
127 | ||||
119 | <%def name="user_actions(user_id, username)"> |
|
128 | <%def name="user_actions(user_id, username)"> | |
120 | ${h.form(h.url('delete_user', id=user_id),method='delete')} |
|
129 | ${h.form(h.url('delete_user', id=user_id),method='delete')} | |
121 | ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id, |
|
130 | ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id, | |
@@ -126,3 +135,9 b'' | |||||
126 | <%def name="user_name(user_id, username)"> |
|
135 | <%def name="user_name(user_id, username)"> | |
127 | ${h.link_to(username,h.url('edit_user', id=user_id))} |
|
136 | ${h.link_to(username,h.url('edit_user', id=user_id))} | |
128 | </%def> |
|
137 | </%def> | |
|
138 | ||||
|
139 | <%def name="toggle_follow(repo_id)"> | |||
|
140 | <span id="follow_toggle_${repo_id}" class="following" title="${_('Stop following this repository')}" | |||
|
141 | onclick="javascript:toggleFollowingRepo(this, ${repo_id},'${str(h.get_token())}')"> | |||
|
142 | </span> | |||
|
143 | </%def> |
@@ -1,9 +1,10 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 | <%inherit file="main.html"/> |
|
2 | <%inherit file="main.html"/> | |
3 |
|
3 | |||
4 | ${_('User %s opened pull request for repository %s and wants you to review changes.') % ('<b>%s</b>' % pr_user_created,pr_repo_url)} |
|
4 | ${_('User %s opened pull request for repository %s and wants you to review changes.') % (('<b>%s</b>' % pr_user_created),pr_repo_url) |n} | |
5 | <div>${_('title')}: ${pr_title}</div> |
|
5 | <div>${_('title')}: ${pr_title}</div> | |
6 | <div>${_('description')}:</div> |
|
6 | <div>${_('description')}:</div> | |
|
7 | <div>${_('View this pull request here')}: ${pr_url}</div> | |||
7 | <p> |
|
8 | <p> | |
8 | ${body} |
|
9 | ${body} | |
9 | </p> |
|
10 | </p> | |
@@ -14,5 +15,3 b'' | |||||
14 | <li>${r}</li> |
|
15 | <li>${r}</li> | |
15 | %endfor |
|
16 | %endfor | |
16 | </ul> |
|
17 | </ul> | |
17 |
|
||||
18 | ${_('View this pull request here')}: ${pr_url} |
|
@@ -86,15 +86,6 b' YUE.onDOMReady(function(){' | |||||
86 | } |
|
86 | } | |
87 | highlight_lines(h_lines); |
|
87 | highlight_lines(h_lines); | |
88 |
|
88 | |||
89 | //remember original location |
|
|||
90 | var old_hash = location.href.substring(location.href.indexOf('#')); |
|
|||
91 |
|
||||
92 | // this makes a jump to anchor moved by 3 posstions for padding |
|
|||
93 | window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1); |
|
|||
94 |
|
||||
95 | //sets old anchor |
|
|||
96 | window.location.hash = old_hash; |
|
|||
97 |
|
||||
98 | } |
|
89 | } | |
99 |
|
90 | |||
100 | // select code link event |
|
91 | // select code link event |
@@ -51,9 +51,9 b'' | |||||
51 | ##<td><b>${gr.repositories_recursive_count}</b></td> |
|
51 | ##<td><b>${gr.repositories_recursive_count}</b></td> | |
52 | </tr> |
|
52 | </tr> | |
53 | % endfor |
|
53 | % endfor | |
54 |
|
||||
55 | </table> |
|
54 | </table> | |
56 | </div> |
|
55 | </div> | |
|
56 | <div id="group-user-paginator" style="padding: 0px 0px 0px 0px"></div> | |||
57 | <div style="height: 20px"></div> |
|
57 | <div style="height: 20px"></div> | |
58 | % endif |
|
58 | % endif | |
59 | <div id="welcome" style="display:none;text-align:center"> |
|
59 | <div id="welcome" style="display:none;text-align:center"> | |
@@ -127,9 +127,6 b'' | |||||
127 | % if c.visual.lightweight_dashboard is False: |
|
127 | % if c.visual.lightweight_dashboard is False: | |
128 | <script> |
|
128 | <script> | |
129 | YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0}; |
|
129 | YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0}; | |
130 | var func = function(node){ |
|
|||
131 | return node.parentNode.parentNode.parentNode.parentNode; |
|
|||
132 | } |
|
|||
133 |
|
130 | |||
134 | // groups table sorting |
|
131 | // groups table sorting | |
135 | var myColumnDefs = [ |
|
132 | var myColumnDefs = [ | |
@@ -151,7 +148,7 b'' | |||||
151 | var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{ |
|
148 | var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{ | |
152 | sortedBy:{key:"name",dir:"asc"}, |
|
149 | sortedBy:{key:"name",dir:"asc"}, | |
153 | paginator: new YAHOO.widget.Paginator({ |
|
150 | paginator: new YAHOO.widget.Paginator({ | |
154 | rowsPerPage: 5, |
|
151 | rowsPerPage: 50, | |
155 | alwaysVisible: false, |
|
152 | alwaysVisible: false, | |
156 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", |
|
153 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", | |
157 | pageLinks: 5, |
|
154 | pageLinks: 5, | |
@@ -162,7 +159,7 b'' | |||||
162 | previousPageLinkLabel: '<', |
|
159 | previousPageLinkLabel: '<', | |
163 | firstPageLinkLabel: '<<', |
|
160 | firstPageLinkLabel: '<<', | |
164 | lastPageLinkLabel: '>>', |
|
161 | lastPageLinkLabel: '>>', | |
165 | containers:['user-paginator'] |
|
162 | containers:['group-user-paginator'] | |
166 | }), |
|
163 | }), | |
167 | MSG_SORTASC:"${_('Click to sort ascending')}", |
|
164 | MSG_SORTASC:"${_('Click to sort ascending')}", | |
168 | MSG_SORTDESC:"${_('Click to sort descending')}" |
|
165 | MSG_SORTDESC:"${_('Click to sort descending')}" | |
@@ -214,13 +211,15 b'' | |||||
214 | myDataTable.subscribe('postRenderEvent',function(oArgs) { |
|
211 | myDataTable.subscribe('postRenderEvent',function(oArgs) { | |
215 | tooltip_activate(); |
|
212 | tooltip_activate(); | |
216 | quick_repo_menu(); |
|
213 | quick_repo_menu(); | |
|
214 | var func = function(node){ | |||
|
215 | return node.parentNode.parentNode.parentNode.parentNode; | |||
|
216 | } | |||
217 | q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func); |
|
217 | q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func); | |
218 | }); |
|
218 | }); | |
219 |
|
219 | |||
220 | </script> |
|
220 | </script> | |
221 | % else: |
|
221 | % else: | |
222 | <script> |
|
222 | <script> | |
223 | //var url = "${h.url('formatted_users', format='json')}"; |
|
|||
224 | var data = ${c.data|n}; |
|
223 | var data = ${c.data|n}; | |
225 | var myDataSource = new YAHOO.util.DataSource(data); |
|
224 | var myDataSource = new YAHOO.util.DataSource(data); | |
226 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON; |
|
225 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON; | |
@@ -233,6 +232,7 b'' | |||||
233 | {key:"name"}, |
|
232 | {key:"name"}, | |
234 | {key:"desc"}, |
|
233 | {key:"desc"}, | |
235 | {key:"last_change"}, |
|
234 | {key:"last_change"}, | |
|
235 | {key:"last_changeset"}, | |||
236 | {key:"owner"}, |
|
236 | {key:"owner"}, | |
237 | {key:"rss"}, |
|
237 | {key:"rss"}, | |
238 | {key:"atom"}, |
|
238 | {key:"atom"}, | |
@@ -266,6 +266,8 b'' | |||||
266 | {key:"desc",label:"${_('Description')}",sortable:true}, |
|
266 | {key:"desc",label:"${_('Description')}",sortable:true}, | |
267 | {key:"last_change",label:"${_('Last Change')}",sortable:true, |
|
267 | {key:"last_change",label:"${_('Last Change')}",sortable:true, | |
268 | sortOptions: { sortFunction: ageSort }}, |
|
268 | sortOptions: { sortFunction: ageSort }}, | |
|
269 | {key:"last_changeset",label:"${_('Tip')}",sortable:true, | |||
|
270 | sortOptions: { sortFunction: revisionSort }}, | |||
269 | {key:"owner",label:"${_('Owner')}",sortable:true}, |
|
271 | {key:"owner",label:"${_('Owner')}",sortable:true}, | |
270 | {key:"rss",label:"",sortable:false}, |
|
272 | {key:"rss",label:"",sortable:false}, | |
271 | {key:"atom",label:"",sortable:false}, |
|
273 | {key:"atom",label:"",sortable:false}, | |
@@ -308,7 +310,7 b'' | |||||
308 |
|
310 | |||
309 | // Reset sort |
|
311 | // Reset sort | |
310 | var state = myDataTable.getState(); |
|
312 | var state = myDataTable.getState(); | |
311 |
|
|
313 | state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC}; | |
312 |
|
314 | |||
313 | // Get filtered data |
|
315 | // Get filtered data | |
314 | myDataSource.sendRequest(YUD.get('q_filter').value,{ |
|
316 | myDataSource.sendRequest(YUD.get('q_filter').value,{ | |
@@ -320,7 +322,11 b'' | |||||
320 |
|
322 | |||
321 | }; |
|
323 | }; | |
322 | YUE.on('q_filter','click',function(){ |
|
324 | YUE.on('q_filter','click',function(){ | |
323 |
YUD. |
|
325 | if(!YUD.hasClass('q_filter', 'loaded')){ | |
|
326 | YUD.get('q_filter').value = ''; | |||
|
327 | //TODO: load here full list later to do search within groups | |||
|
328 | YUD.addClass('q_filter', 'loaded'); | |||
|
329 | } | |||
324 | }); |
|
330 | }); | |
325 |
|
331 | |||
326 | YUE.on('q_filter','keyup',function (e) { |
|
332 | YUE.on('q_filter','keyup',function (e) { |
@@ -43,78 +43,41 b'' | |||||
43 | </div> |
|
43 | </div> | |
44 | <div class="box box-right"> |
|
44 | <div class="box box-right"> | |
45 | <!-- box / title --> |
|
45 | <!-- box / title --> | |
|
46 | ||||
46 | <div class="title"> |
|
47 | <div class="title"> | |
47 | <h5> |
|
48 | <h5> | |
48 | <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> |
|
49 | <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/> | |
49 | <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a> / <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> |
|
50 | <input class="q_filter_box" id="q_filter_watched" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/> | |
50 | </h5> |
|
51 | </h5> | |
51 | %if h.HasPermissionAny('hg.admin','hg.create.repository')(): |
|
52 | <ul class="links" style="color:#DADADA"> | |
52 | <ul class="links"> |
|
|||
53 | <li> |
|
53 | <li> | |
54 | <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span> |
|
54 | <span><a id="show_watched" class="link-white current" href="#watched">${_('Watched')}</a> </span> | |
55 | </li> |
|
55 | </li> | |
|
56 | <li> | |||
|
57 | <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span> | |||
|
58 | </li> | |||
|
59 | %if h.HasPermissionAny('hg.admin','hg.create.repository')(): | |||
|
60 | <li> | |||
|
61 | <span>${h.link_to(_('Add repo'),h.url('admin_settings_create_repository'))}</span> | |||
|
62 | </li> | |||
|
63 | %endif | |||
56 | </ul> |
|
64 | </ul> | |
57 | %endif |
|
|||
58 | </div> |
|
|||
59 | <!-- end box / title --> |
|
|||
60 | <div id="my" class="table" style="display:none"> |
|
|||
61 | ## loaded via AJAX |
|
|||
62 | ${_('Loading...')} |
|
|||
63 | </div> |
|
65 | </div> | |
64 |
|
66 | |||
65 | <div id="watched" class="table"> |
|
67 | <!-- end box / title --> | |
66 | %if c.following: |
|
68 | <div id="my_container" style="display:none"> | |
67 | <table> |
|
69 | <div class="table yui-skin-sam" id="repos_list_wrap"></div> | |
68 | <thead> |
|
70 | <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div> | |
69 | <tr> |
|
71 | </div> | |
70 | <th class="left">${_('Name')}</th> |
|
|||
71 | </thead> |
|
|||
72 | <tbody> |
|
|||
73 | %for entry in c.following: |
|
|||
74 | <tr> |
|
|||
75 | <td> |
|
|||
76 | %if entry.follows_user_id: |
|
|||
77 | <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/> |
|
|||
78 | ${entry.follows_user.full_contact} |
|
|||
79 | %endif |
|
|||
80 |
|
||||
81 | %if entry.follows_repo_id: |
|
|||
82 | <div style="float:right;padding-right:5px"> |
|
|||
83 | <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}" |
|
|||
84 | onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')"> |
|
|||
85 | </span> |
|
|||
86 | </div> |
|
|||
87 |
|
72 | |||
88 | %if h.is_hg(entry.follows_repository): |
|
73 | <div id="watched_container"> | |
89 | <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/> |
|
74 | <div class="table yui-skin-sam" id="watched_repos_list_wrap"></div> | |
90 | %elif h.is_git(entry.follows_repository): |
|
75 | <div id="watched-user-paginator" style="padding: 0px 0px 0px 20px"></div> | |
91 | <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/> |
|
|||
92 | %endif |
|
|||
93 |
|
||||
94 | %if entry.follows_repository.private and c.visual.show_private_icon: |
|
|||
95 | <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/> |
|
|||
96 | %elif not entry.follows_repository.private and c.visual.show_public_icon: |
|
|||
97 | <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/> |
|
|||
98 | %endif |
|
|||
99 | <span class="watched_repo"> |
|
|||
100 | ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))} |
|
|||
101 | </span> |
|
|||
102 | %endif |
|
|||
103 | </td> |
|
|||
104 | </tr> |
|
|||
105 | %endfor |
|
|||
106 | </tbody> |
|
|||
107 | </table> |
|
|||
108 | %else: |
|
|||
109 | <div style="padding:5px 0px 10px 0px;"> |
|
|||
110 | ${_('You are not following any users or repositories')} |
|
|||
111 | </div> |
|
|||
112 | %endif |
|
|||
113 | </div> |
|
76 | </div> | |
114 | </div> |
|
77 | </div> | |
115 |
|
78 | |||
116 | <script type="text/javascript"> |
|
79 | <script type="text/javascript"> | |
117 |
|
80 | |||
118 | YUE.on('j_filter','click',function(){ |
|
81 | YUE.on('j_filter','click',function(){ | |
119 | var jfilter = YUD.get('j_filter'); |
|
82 | var jfilter = YUD.get('j_filter'); | |
120 | if(YUD.hasClass(jfilter, 'initial')){ |
|
83 | if(YUD.hasClass(jfilter, 'initial')){ | |
@@ -132,31 +95,49 b'' | |||||
132 | var val = YUD.get('j_filter').value; |
|
95 | var val = YUD.get('j_filter').value; | |
133 | window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val); |
|
96 | window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val); | |
134 | }); |
|
97 | }); | |
135 |
fix_j_filter_width(YUD.get('j_filter').value.length); |
|
98 | fix_j_filter_width(YUD.get('j_filter').value.length); | |
136 |
|
||||
137 | var show_my = function(e){ |
|
|||
138 | YUD.setStyle('watched','display','none'); |
|
|||
139 | YUD.setStyle('my','display',''); |
|
|||
140 |
|
99 | |||
141 | var url = "${h.url('admin_settings_my_repos')}"; |
|
100 | YUE.on('refresh','click',function(e){ | |
142 | ypjax(url, 'my', function(){ |
|
101 | ypjax("${h.url.current(filter=c.search_term)}","journal",function(){ | |
|
102 | show_more_event(); | |||
143 | tooltip_activate(); |
|
103 | tooltip_activate(); | |
144 | quick_repo_menu(); |
|
104 | show_changeset_tooltip(); | |
145 | var nodes = YUQ('#my tr td a.repo_name'); |
|
105 | }); | |
146 | var func = function(node){ |
|
106 | YUE.preventDefault(e); | |
147 | return node.parentNode.parentNode.parentNode; |
|
107 | }); | |
148 | } |
|
|||
149 | q_filter('q_filter',nodes,func); |
|
|||
150 | }); |
|
|||
151 |
|
108 | |||
|
109 | var show_my = function(e){ | |||
|
110 | YUD.setStyle('watched_container','display','none'); | |||
|
111 | YUD.setStyle('my_container','display',''); | |||
|
112 | YUD.setStyle('q_filter','display',''); | |||
|
113 | YUD.setStyle('q_filter_watched','display','none'); | |||
|
114 | ||||
|
115 | YUD.addClass('show_my', 'current'); | |||
|
116 | YUD.removeClass('show_watched','current'); | |||
|
117 | ||||
|
118 | if(!YUD.hasClass('show_my', 'loaded')){ | |||
|
119 | table_renderer(${c.data |n}); | |||
|
120 | YUD.addClass('show_my', 'loaded'); | |||
|
121 | } | |||
152 | } |
|
122 | } | |
153 | YUE.on('show_my','click',function(e){ |
|
123 | YUE.on('show_my','click',function(e){ | |
154 | show_my(e); |
|
124 | show_my(e); | |
155 | }) |
|
125 | }) | |
156 | var show_watched = function(e){ |
|
126 | var show_watched = function(e){ | |
157 | YUD.setStyle('my','display','none'); |
|
127 | YUD.setStyle('my_container','display','none'); | |
158 | YUD.setStyle('watched','display',''); |
|
128 | YUD.setStyle('watched_container','display',''); | |
159 | var nodes = YUQ('#watched .watched_repo a'); |
|
129 | YUD.setStyle('q_filter_watched','display',''); | |
|
130 | YUD.setStyle('q_filter','display','none'); | |||
|
131 | ||||
|
132 | YUD.addClass('show_watched', 'current'); | |||
|
133 | YUD.removeClass('show_my','current'); | |||
|
134 | if(!YUD.hasClass('show_watched', 'loaded')){ | |||
|
135 | watched_renderer(${c.watched_data |n}); | |||
|
136 | YUD.addClass('show_watched', 'loaded'); | |||
|
137 | } | |||
|
138 | ||||
|
139 | return | |||
|
140 | var nodes = YUQ('#watched_container .watched_repo a'); | |||
160 | var target = 'q_filter'; |
|
141 | var target = 'q_filter'; | |
161 | var func = function(node){ |
|
142 | var func = function(node){ | |
162 | return node.parentNode.parentNode; |
|
143 | return node.parentNode.parentNode; | |
@@ -177,62 +158,218 b'' | |||||
177 | if (url[1]) { |
|
158 | if (url[1]) { | |
178 | //We have a hash |
|
159 | //We have a hash | |
179 | var tabHash = url[1]; |
|
160 | var tabHash = url[1]; | |
180 |
tabs[tabHash] |
|
161 | var func = tabs[tabHash] | |
|
162 | if (func){ | |||
|
163 | func(); | |||
|
164 | } | |||
181 | } |
|
165 | } | |
|
166 | function watched_renderer(data){ | |||
|
167 | var myDataSource = new YAHOO.util.DataSource(data); | |||
|
168 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON; | |||
182 |
|
169 | |||
183 | YUE.on('refresh','click',function(e){ |
|
170 | myDataSource.responseSchema = { | |
184 | ypjax("${h.url.current(filter=c.search_term)}","journal",function(){ |
|
171 | resultsList: "records", | |
185 | show_more_event(); |
|
172 | fields: [ | |
186 | tooltip_activate(); |
|
173 | {key:"menu"}, | |
187 | show_changeset_tooltip(); |
|
174 | {key:"raw_name"}, | |
188 | }); |
|
175 | {key:"name"}, | |
189 | YUE.preventDefault(e); |
|
176 | {key:"last_changeset"}, | |
190 | }); |
|
177 | {key:"action"}, | |
|
178 | ] | |||
|
179 | }; | |||
|
180 | myDataSource.doBeforeCallback = function(req,raw,res,cb) { | |||
|
181 | // This is the filter function | |||
|
182 | var data = res.results || [], | |||
|
183 | filtered = [], | |||
|
184 | i,l; | |||
191 |
|
185 | |||
|
186 | if (req) { | |||
|
187 | req = req.toLowerCase(); | |||
|
188 | for (i = 0; i<data.length; i++) { | |||
|
189 | var pos = data[i].raw_name.toLowerCase().indexOf(req) | |||
|
190 | if (pos != -1) { | |||
|
191 | filtered.push(data[i]); | |||
|
192 | } | |||
|
193 | } | |||
|
194 | res.results = filtered; | |||
|
195 | } | |||
|
196 | return res; | |||
|
197 | } | |||
|
198 | // main table sorting | |||
|
199 | var myColumnDefs = [ | |||
|
200 | {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"}, | |||
|
201 | {key:"name",label:"${_('Name')}",sortable:true, | |||
|
202 | sortOptions: { sortFunction: nameSort }}, | |||
|
203 | {key:"last_changeset",label:"${_('Tip')}",sortable:true, | |||
|
204 | sortOptions: { sortFunction: revisionSort }}, | |||
|
205 | {key:"action",label:"${_('Action')}",sortable:false}, | |||
|
206 | ]; | |||
192 |
|
207 | |||
193 | // main table sorting |
|
208 | var myDataTable = new YAHOO.widget.DataTable("watched_repos_list_wrap", myColumnDefs, myDataSource,{ | |
194 | var myColumnDefs = [ |
|
209 | sortedBy:{key:"name",dir:"asc"}, | |
195 | {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"}, |
|
210 | paginator: new YAHOO.widget.Paginator({ | |
196 | {key:"name",label:"${_('Name')}",sortable:true, |
|
211 | rowsPerPage: 25, | |
197 | sortOptions: { sortFunction: nameSort }}, |
|
212 | alwaysVisible: false, | |
198 | {key:"tip",label:"${_('Tip')}",sortable:true, |
|
213 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", | |
199 | sortOptions: { sortFunction: revisionSort }}, |
|
214 | pageLinks: 5, | |
200 | {key:"action1",label:"",sortable:false}, |
|
215 | containerClass: 'pagination-wh', | |
201 | {key:"action2",label:"",sortable:false}, |
|
216 | currentPageClass: 'pager_curpage', | |
202 | ]; |
|
217 | pageLinkClass: 'pager_link', | |
|
218 | nextPageLinkLabel: '>', | |||
|
219 | previousPageLinkLabel: '<', | |||
|
220 | firstPageLinkLabel: '<<', | |||
|
221 | lastPageLinkLabel: '>>', | |||
|
222 | containers:['watched-user-paginator'] | |||
|
223 | }), | |||
203 |
|
224 | |||
204 | var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list")); |
|
225 | MSG_SORTASC:"${_('Click to sort ascending')}", | |
|
226 | MSG_SORTDESC:"${_('Click to sort descending')}", | |||
|
227 | MSG_EMPTY:"${_('No records found.')}", | |||
|
228 | MSG_ERROR:"${_('Data error.')}", | |||
|
229 | MSG_LOADING:"${_('Loading...')}", | |||
|
230 | } | |||
|
231 | ); | |||
|
232 | myDataTable.subscribe('postRenderEvent',function(oArgs) { | |||
|
233 | tooltip_activate(); | |||
|
234 | quick_repo_menu(); | |||
|
235 | }); | |||
|
236 | ||||
|
237 | var filterTimeout = null; | |||
|
238 | ||||
|
239 | updateFilter = function () { | |||
|
240 | // Reset timeout | |||
|
241 | filterTimeout = null; | |||
205 |
|
242 | |||
206 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; |
|
243 | // Reset sort | |
|
244 | var state = myDataTable.getState(); | |||
|
245 | state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC}; | |||
|
246 | ||||
|
247 | // Get filtered data | |||
|
248 | myDataSource.sendRequest(YUD.get('q_filter_watched').value,{ | |||
|
249 | success : myDataTable.onDataReturnInitializeTable, | |||
|
250 | failure : myDataTable.onDataReturnInitializeTable, | |||
|
251 | scope : myDataTable, | |||
|
252 | argument: state | |||
|
253 | }); | |||
|
254 | ||||
|
255 | }; | |||
|
256 | YUE.on('q_filter_watched','click',function(){ | |||
|
257 | if(!YUD.hasClass('q_filter_watched', 'loaded')){ | |||
|
258 | YUD.get('q_filter_watched').value = ''; | |||
|
259 | //TODO: load here full list later to do search within groups | |||
|
260 | YUD.addClass('q_filter_watched', 'loaded'); | |||
|
261 | } | |||
|
262 | }); | |||
207 |
|
263 | |||
208 | myDataSource.responseSchema = { |
|
264 | YUE.on('q_filter_watched','keyup',function (e) { | |
209 | fields: [ |
|
265 | clearTimeout(filterTimeout); | |
210 | {key:"menu"}, |
|
266 | filterTimeout = setTimeout(updateFilter,600); | |
211 | {key:"name"}, |
|
267 | }); | |
212 | {key:"tip"}, |
|
268 | } | |
213 | {key:"action1"}, |
|
269 | ||
214 | {key:"action2"} |
|
270 | function table_renderer(data){ | |
215 | ] |
|
271 | var myDataSource = new YAHOO.util.DataSource(data); | |
216 | }; |
|
272 | myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON; | |
|
273 | ||||
|
274 | myDataSource.responseSchema = { | |||
|
275 | resultsList: "records", | |||
|
276 | fields: [ | |||
|
277 | {key:"menu"}, | |||
|
278 | {key:"raw_name"}, | |||
|
279 | {key:"name"}, | |||
|
280 | {key:"last_changeset"}, | |||
|
281 | {key:"action"}, | |||
|
282 | ] | |||
|
283 | }; | |||
|
284 | myDataSource.doBeforeCallback = function(req,raw,res,cb) { | |||
|
285 | // This is the filter function | |||
|
286 | var data = res.results || [], | |||
|
287 | filtered = [], | |||
|
288 | i,l; | |||
|
289 | ||||
|
290 | if (req) { | |||
|
291 | req = req.toLowerCase(); | |||
|
292 | for (i = 0; i<data.length; i++) { | |||
|
293 | var pos = data[i].raw_name.toLowerCase().indexOf(req) | |||
|
294 | if (pos != -1) { | |||
|
295 | filtered.push(data[i]); | |||
|
296 | } | |||
|
297 | } | |||
|
298 | res.results = filtered; | |||
|
299 | } | |||
|
300 | return res; | |||
|
301 | } | |||
|
302 | // main table sorting | |||
|
303 | var myColumnDefs = [ | |||
|
304 | {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"}, | |||
|
305 | {key:"name",label:"${_('Name')}",sortable:true, | |||
|
306 | sortOptions: { sortFunction: nameSort }}, | |||
|
307 | {key:"last_changeset",label:"${_('Tip')}",sortable:true, | |||
|
308 | sortOptions: { sortFunction: revisionSort }}, | |||
|
309 | {key:"action",label:"${_('Action')}",sortable:false}, | |||
|
310 | ]; | |||
217 |
|
311 | |||
218 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource, |
|
312 | var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ | |
219 | { |
|
313 | sortedBy:{key:"name",dir:"asc"}, | |
220 | sortedBy:{key:"name",dir:"asc"}, |
|
314 | paginator: new YAHOO.widget.Paginator({ | |
221 | MSG_SORTASC:"${_('Click to sort ascending')}", |
|
315 | rowsPerPage: 25, | |
222 | MSG_SORTDESC:"${_('Click to sort descending')}", |
|
316 | alwaysVisible: false, | |
223 | MSG_EMPTY:"${_('No records found.')}", |
|
317 | template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", | |
224 | MSG_ERROR:"${_('Data error.')}", |
|
318 | pageLinks: 5, | |
225 | MSG_LOADING:"${_('Loading...')}", |
|
319 | containerClass: 'pagination-wh', | |
|
320 | currentPageClass: 'pager_curpage', | |||
|
321 | pageLinkClass: 'pager_link', | |||
|
322 | nextPageLinkLabel: '>', | |||
|
323 | previousPageLinkLabel: '<', | |||
|
324 | firstPageLinkLabel: '<<', | |||
|
325 | lastPageLinkLabel: '>>', | |||
|
326 | containers:['user-paginator'] | |||
|
327 | }), | |||
|
328 | ||||
|
329 | MSG_SORTASC:"${_('Click to sort ascending')}", | |||
|
330 | MSG_SORTDESC:"${_('Click to sort descending')}", | |||
|
331 | MSG_EMPTY:"${_('No records found.')}", | |||
|
332 | MSG_ERROR:"${_('Data error.')}", | |||
|
333 | MSG_LOADING:"${_('Loading...')}", | |||
|
334 | } | |||
|
335 | ); | |||
|
336 | myDataTable.subscribe('postRenderEvent',function(oArgs) { | |||
|
337 | tooltip_activate(); | |||
|
338 | quick_repo_menu(); | |||
|
339 | }); | |||
|
340 | ||||
|
341 | var filterTimeout = null; | |||
|
342 | ||||
|
343 | updateFilter = function () { | |||
|
344 | // Reset timeout | |||
|
345 | filterTimeout = null; | |||
|
346 | ||||
|
347 | // Reset sort | |||
|
348 | var state = myDataTable.getState(); | |||
|
349 | state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC}; | |||
|
350 | ||||
|
351 | // Get filtered data | |||
|
352 | myDataSource.sendRequest(YUD.get('q_filter').value,{ | |||
|
353 | success : myDataTable.onDataReturnInitializeTable, | |||
|
354 | failure : myDataTable.onDataReturnInitializeTable, | |||
|
355 | scope : myDataTable, | |||
|
356 | argument: state | |||
|
357 | }); | |||
|
358 | ||||
|
359 | }; | |||
|
360 | YUE.on('q_filter','click',function(){ | |||
|
361 | if(!YUD.hasClass('q_filter', 'loaded')){ | |||
|
362 | YUD.get('q_filter').value = ''; | |||
|
363 | //TODO: load here full list later to do search within groups | |||
|
364 | YUD.addClass('q_filter', 'loaded'); | |||
226 | } |
|
365 | } | |
227 | ); |
|
366 | }); | |
228 | myDataTable.subscribe('postRenderEvent',function(oArgs) { |
|
367 | ||
229 | tooltip_activate(); |
|
368 | YUE.on('q_filter','keyup',function (e) { | |
230 | quick_repo_menu(); |
|
369 | clearTimeout(filterTimeout); | |
231 | var func = function(node){ |
|
370 | filterTimeout = setTimeout(updateFilter,600); | |
232 | return node.parentNode.parentNode.parentNode.parentNode; |
|
371 | }); | |
233 |
|
|
372 | } | |
234 | q_filter('q_filter',YUQ('#my tr td a.repo_name'),func); |
|
|||
235 | }); |
|
|||
236 |
|
373 | |||
237 | </script> |
|
374 | </script> | |
238 | </%def> |
|
375 | </%def> |
@@ -51,6 +51,24 b'' | |||||
51 | %endif |
|
51 | %endif | |
52 | </div> |
|
52 | </div> | |
53 | </div> |
|
53 | </div> | |
|
54 | <div class="field"> | |||
|
55 | <div class="label-summary"> | |||
|
56 | <label>${_('Origin repository')}:</label> | |||
|
57 | </div> | |||
|
58 | <div class="input"> | |||
|
59 | <div> | |||
|
60 | ##%if h.is_hg(c.pull_request.org_repo): | |||
|
61 | ## <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/> | |||
|
62 | ##%elif h.is_git(c.pull_request.org_repo): | |||
|
63 | ## <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/> | |||
|
64 | ##%endif | |||
|
65 | <span class="spantag">${c.pull_request.org_ref_parts[0]}</span> | |||
|
66 | : | |||
|
67 | <span class="spantag">${c.pull_request.org_ref_parts[1]}</span> | |||
|
68 | <span><a href="${h.url('summary_home', repo_name=c.pull_request.org_repo.repo_name)}">${c.pull_request.org_repo.clone_url()}</a></span> | |||
|
69 | </div> | |||
|
70 | </div> | |||
|
71 | </div> | |||
54 | </div> |
|
72 | </div> | |
55 | </div> |
|
73 | </div> | |
56 | <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div> |
|
74 | <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div> | |
@@ -198,7 +216,7 b'' | |||||
198 | // inject comments into they proper positions |
|
216 | // inject comments into they proper positions | |
199 | var file_comments = YUQ('.inline-comment-placeholder'); |
|
217 | var file_comments = YUQ('.inline-comment-placeholder'); | |
200 | renderInlineComments(file_comments); |
|
218 | renderInlineComments(file_comments); | |
201 |
|
219 | |||
202 | YUE.on(YUD.get('update_pull_request'),'click',function(e){ |
|
220 | YUE.on(YUD.get('update_pull_request'),'click',function(e){ | |
203 | updateReviewers(); |
|
221 | updateReviewers(); | |
204 | }) |
|
222 | }) |
@@ -94,7 +94,7 b'' | |||||
94 | ${h.submit('save',_('Save'),class_="ui-btn large")} |
|
94 | ${h.submit('save',_('Save'),class_="ui-btn large")} | |
95 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} |
|
95 | ${h.reset('reset',_('Reset'),class_="ui-btn large")} | |
96 | </div> |
|
96 | </div> | |
97 |
|
97 | |||
98 | </div> |
|
98 | </div> | |
99 | ${h.end_form()} |
|
99 | ${h.end_form()} | |
100 | </div> |
|
100 | </div> |
@@ -59,13 +59,13 b' def destroy_users_group(name=TEST_USERS_' | |||||
59 | Session().commit() |
|
59 | Session().commit() | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | def create_repo(repo_name, repo_type): |
|
62 | def create_repo(repo_name, repo_type, owner=None): | |
63 | # create new repo |
|
63 | # create new repo | |
64 | form_data = _get_repo_create_params( |
|
64 | form_data = _get_repo_create_params( | |
65 | repo_name_full=repo_name, |
|
65 | repo_name_full=repo_name, | |
66 | repo_description='description %s' % repo_name, |
|
66 | repo_description='description %s' % repo_name, | |
67 | ) |
|
67 | ) | |
68 | cur_user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
68 | cur_user = UserModel().get_by_username(owner or TEST_USER_ADMIN_LOGIN) | |
69 | r = RepoModel().create(form_data, cur_user) |
|
69 | r = RepoModel().create(form_data, cur_user) | |
70 | Session().commit() |
|
70 | Session().commit() | |
71 | return r |
|
71 | return r | |
@@ -93,7 +93,7 b' class BaseTestApi(object):' | |||||
93 | def setUpClass(self): |
|
93 | def setUpClass(self): | |
94 | self.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
94 | self.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) | |
95 | self.apikey = self.usr.api_key |
|
95 | self.apikey = self.usr.api_key | |
96 |
self. |
|
96 | self.test_user = UserModel().create_or_update( | |
97 | username='test-api', |
|
97 | username='test-api', | |
98 | password='test', |
|
98 | password='test', | |
99 | email='test@api.rhodecode.org', |
|
99 | email='test@api.rhodecode.org', | |
@@ -101,7 +101,8 b' class BaseTestApi(object):' | |||||
101 | lastname='last' |
|
101 | lastname='last' | |
102 | ) |
|
102 | ) | |
103 | Session().commit() |
|
103 | Session().commit() | |
104 |
self.TEST_USER_LOGIN = self. |
|
104 | self.TEST_USER_LOGIN = self.test_user.username | |
|
105 | self.apikey_regular = self.test_user.api_key | |||
105 |
|
106 | |||
106 | @classmethod |
|
107 | @classmethod | |
107 | def teardownClass(self): |
|
108 | def teardownClass(self): | |
@@ -148,12 +149,40 b' class BaseTestApi(object):' | |||||
148 | self._compare_error(id_, expected, given=response.body) |
|
149 | self._compare_error(id_, expected, given=response.body) | |
149 |
|
150 | |||
150 | def test_api_missing_non_optional_param(self): |
|
151 | def test_api_missing_non_optional_param(self): | |
151 |
id_, params = _build_data(self.apikey, 'get_ |
|
152 | id_, params = _build_data(self.apikey, 'get_repo') | |
|
153 | response = api_call(self, params) | |||
|
154 | ||||
|
155 | expected = 'Missing non optional `repoid` arg in JSON DATA' | |||
|
156 | self._compare_error(id_, expected, given=response.body) | |||
|
157 | ||||
|
158 | def test_api_missing_non_optional_param_args_null(self): | |||
|
159 | id_, params = _build_data(self.apikey, 'get_repo') | |||
|
160 | params = params.replace('"args": {}', '"args": null') | |||
152 | response = api_call(self, params) |
|
161 | response = api_call(self, params) | |
153 |
|
162 | |||
154 |
expected = 'Missing non optional ` |
|
163 | expected = 'Missing non optional `repoid` arg in JSON DATA' | |
|
164 | self._compare_error(id_, expected, given=response.body) | |||
|
165 | ||||
|
166 | def test_api_missing_non_optional_param_args_bad(self): | |||
|
167 | id_, params = _build_data(self.apikey, 'get_repo') | |||
|
168 | params = params.replace('"args": {}', '"args": 1') | |||
|
169 | response = api_call(self, params) | |||
|
170 | ||||
|
171 | expected = 'Missing non optional `repoid` arg in JSON DATA' | |||
155 | self._compare_error(id_, expected, given=response.body) |
|
172 | self._compare_error(id_, expected, given=response.body) | |
156 |
|
173 | |||
|
174 | def test_api_args_is_null(self): | |||
|
175 | id_, params = _build_data(self.apikey, 'get_users',) | |||
|
176 | params = params.replace('"args": {}', '"args": null') | |||
|
177 | response = api_call(self, params) | |||
|
178 | self.assertEqual(response.status, '200 OK') | |||
|
179 | ||||
|
180 | def test_api_args_is_bad(self): | |||
|
181 | id_, params = _build_data(self.apikey, 'get_users',) | |||
|
182 | params = params.replace('"args": {}', '"args": 1') | |||
|
183 | response = api_call(self, params) | |||
|
184 | self.assertEqual(response.status, '200 OK') | |||
|
185 | ||||
157 | def test_api_get_users(self): |
|
186 | def test_api_get_users(self): | |
158 | id_, params = _build_data(self.apikey, 'get_users',) |
|
187 | id_, params = _build_data(self.apikey, 'get_users',) | |
159 | response = api_call(self, params) |
|
188 | response = api_call(self, params) | |
@@ -184,6 +213,36 b' class BaseTestApi(object):' | |||||
184 | expected = "user `%s` does not exist" % 'trololo' |
|
213 | expected = "user `%s` does not exist" % 'trololo' | |
185 | self._compare_error(id_, expected, given=response.body) |
|
214 | self._compare_error(id_, expected, given=response.body) | |
186 |
|
215 | |||
|
216 | def test_api_get_user_without_giving_userid(self): | |||
|
217 | id_, params = _build_data(self.apikey, 'get_user') | |||
|
218 | response = api_call(self, params) | |||
|
219 | ||||
|
220 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) | |||
|
221 | ret = usr.get_api_data() | |||
|
222 | ret['permissions'] = AuthUser(usr.user_id).permissions | |||
|
223 | ||||
|
224 | expected = ret | |||
|
225 | self._compare_ok(id_, expected, given=response.body) | |||
|
226 | ||||
|
227 | def test_api_get_user_without_giving_userid_non_admin(self): | |||
|
228 | id_, params = _build_data(self.apikey_regular, 'get_user') | |||
|
229 | response = api_call(self, params) | |||
|
230 | ||||
|
231 | usr = UserModel().get_by_username(self.TEST_USER_LOGIN) | |||
|
232 | ret = usr.get_api_data() | |||
|
233 | ret['permissions'] = AuthUser(usr.user_id).permissions | |||
|
234 | ||||
|
235 | expected = ret | |||
|
236 | self._compare_ok(id_, expected, given=response.body) | |||
|
237 | ||||
|
238 | def test_api_get_user_with_giving_userid_non_admin(self): | |||
|
239 | id_, params = _build_data(self.apikey_regular, 'get_user', | |||
|
240 | userid=self.TEST_USER_LOGIN) | |||
|
241 | response = api_call(self, params) | |||
|
242 | ||||
|
243 | expected = 'userid is not the same as your user' | |||
|
244 | self._compare_error(id_, expected, given=response.body) | |||
|
245 | ||||
187 | def test_api_pull(self): |
|
246 | def test_api_pull(self): | |
188 | #TODO: issues with rhodecode_extras here.. not sure why ! |
|
247 | #TODO: issues with rhodecode_extras here.. not sure why ! | |
189 | pass |
|
248 | pass | |
@@ -237,6 +296,42 b' class BaseTestApi(object):' | |||||
237 | % (TEST_USER_ADMIN_LOGIN, self.REPO, True)) |
|
296 | % (TEST_USER_ADMIN_LOGIN, self.REPO, True)) | |
238 | self._compare_ok(id_, expected, given=response.body) |
|
297 | self._compare_ok(id_, expected, given=response.body) | |
239 |
|
298 | |||
|
299 | def test_api_lock_repo_lock_aquire_by_non_admin(self): | |||
|
300 | repo_name = 'api_delete_me' | |||
|
301 | create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN) | |||
|
302 | try: | |||
|
303 | id_, params = _build_data(self.apikey_regular, 'lock', | |||
|
304 | repoid=repo_name, | |||
|
305 | locked=True) | |||
|
306 | response = api_call(self, params) | |||
|
307 | expected = ('User `%s` set lock state for repo `%s` to `%s`' | |||
|
308 | % (self.TEST_USER_LOGIN, repo_name, True)) | |||
|
309 | self._compare_ok(id_, expected, given=response.body) | |||
|
310 | finally: | |||
|
311 | destroy_repo(repo_name) | |||
|
312 | ||||
|
313 | def test_api_lock_repo_lock_aquire_non_admin_with_userid(self): | |||
|
314 | repo_name = 'api_delete_me' | |||
|
315 | create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN) | |||
|
316 | try: | |||
|
317 | id_, params = _build_data(self.apikey_regular, 'lock', | |||
|
318 | userid=TEST_USER_ADMIN_LOGIN, | |||
|
319 | repoid=repo_name, | |||
|
320 | locked=True) | |||
|
321 | response = api_call(self, params) | |||
|
322 | expected = 'userid is not the same as your user' | |||
|
323 | self._compare_error(id_, expected, given=response.body) | |||
|
324 | finally: | |||
|
325 | destroy_repo(repo_name) | |||
|
326 | ||||
|
327 | def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self): | |||
|
328 | id_, params = _build_data(self.apikey_regular, 'lock', | |||
|
329 | repoid=self.REPO, | |||
|
330 | locked=True) | |||
|
331 | response = api_call(self, params) | |||
|
332 | expected = 'repository `%s` does not exist' % (self.REPO) | |||
|
333 | self._compare_error(id_, expected, given=response.body) | |||
|
334 | ||||
240 | def test_api_lock_repo_lock_release(self): |
|
335 | def test_api_lock_repo_lock_release(self): | |
241 | id_, params = _build_data(self.apikey, 'lock', |
|
336 | id_, params = _build_data(self.apikey, 'lock', | |
242 | userid=TEST_USER_ADMIN_LOGIN, |
|
337 | userid=TEST_USER_ADMIN_LOGIN, | |
@@ -247,6 +342,15 b' class BaseTestApi(object):' | |||||
247 | % (TEST_USER_ADMIN_LOGIN, self.REPO, False)) |
|
342 | % (TEST_USER_ADMIN_LOGIN, self.REPO, False)) | |
248 | self._compare_ok(id_, expected, given=response.body) |
|
343 | self._compare_ok(id_, expected, given=response.body) | |
249 |
|
344 | |||
|
345 | def test_api_lock_repo_lock_aquire_optional_userid(self): | |||
|
346 | id_, params = _build_data(self.apikey, 'lock', | |||
|
347 | repoid=self.REPO, | |||
|
348 | locked=True) | |||
|
349 | response = api_call(self, params) | |||
|
350 | expected = ('User `%s` set lock state for repo `%s` to `%s`' | |||
|
351 | % (TEST_USER_ADMIN_LOGIN, self.REPO, True)) | |||
|
352 | self._compare_ok(id_, expected, given=response.body) | |||
|
353 | ||||
250 | @mock.patch.object(Repository, 'lock', crash) |
|
354 | @mock.patch.object(Repository, 'lock', crash) | |
251 | def test_api_lock_error(self): |
|
355 | def test_api_lock_error(self): | |
252 | id_, params = _build_data(self.apikey, 'lock', |
|
356 | id_, params = _build_data(self.apikey, 'lock', | |
@@ -457,6 +561,48 b' class BaseTestApi(object):' | |||||
457 | self._compare_ok(id_, expected, given=response.body) |
|
561 | self._compare_ok(id_, expected, given=response.body) | |
458 | destroy_users_group(new_group) |
|
562 | destroy_users_group(new_group) | |
459 |
|
563 | |||
|
564 | def test_api_get_repo_by_non_admin(self): | |||
|
565 | id_, params = _build_data(self.apikey, 'get_repo', | |||
|
566 | repoid=self.REPO) | |||
|
567 | response = api_call(self, params) | |||
|
568 | ||||
|
569 | repo = RepoModel().get_by_repo_name(self.REPO) | |||
|
570 | ret = repo.get_api_data() | |||
|
571 | ||||
|
572 | members = [] | |||
|
573 | for user in repo.repo_to_perm: | |||
|
574 | perm = user.permission.permission_name | |||
|
575 | user = user.user | |||
|
576 | user_data = user.get_api_data() | |||
|
577 | user_data['type'] = "user" | |||
|
578 | user_data['permission'] = perm | |||
|
579 | members.append(user_data) | |||
|
580 | ||||
|
581 | for users_group in repo.users_group_to_perm: | |||
|
582 | perm = users_group.permission.permission_name | |||
|
583 | users_group = users_group.users_group | |||
|
584 | users_group_data = users_group.get_api_data() | |||
|
585 | users_group_data['type'] = "users_group" | |||
|
586 | users_group_data['permission'] = perm | |||
|
587 | members.append(users_group_data) | |||
|
588 | ||||
|
589 | ret['members'] = members | |||
|
590 | ||||
|
591 | expected = ret | |||
|
592 | self._compare_ok(id_, expected, given=response.body) | |||
|
593 | ||||
|
594 | def test_api_get_repo_by_non_admin_no_permission_to_repo(self): | |||
|
595 | RepoModel().grant_user_permission(repo=self.REPO, | |||
|
596 | user=self.TEST_USER_LOGIN, | |||
|
597 | perm='repository.none') | |||
|
598 | ||||
|
599 | id_, params = _build_data(self.apikey_regular, 'get_repo', | |||
|
600 | repoid=self.REPO) | |||
|
601 | response = api_call(self, params) | |||
|
602 | ||||
|
603 | expected = 'repository `%s` does not exist' % (self.REPO) | |||
|
604 | self._compare_error(id_, expected, given=response.body) | |||
|
605 | ||||
460 | def test_api_get_repo_that_doesn_not_exist(self): |
|
606 | def test_api_get_repo_that_doesn_not_exist(self): | |
461 | id_, params = _build_data(self.apikey, 'get_repo', |
|
607 | id_, params = _build_data(self.apikey, 'get_repo', | |
462 | repoid='no-such-repo') |
|
608 | repoid='no-such-repo') | |
@@ -478,6 +624,18 b' class BaseTestApi(object):' | |||||
478 | expected = ret |
|
624 | expected = ret | |
479 | self._compare_ok(id_, expected, given=response.body) |
|
625 | self._compare_ok(id_, expected, given=response.body) | |
480 |
|
626 | |||
|
627 | def test_api_get_repos_non_admin(self): | |||
|
628 | id_, params = _build_data(self.apikey_regular, 'get_repos') | |||
|
629 | response = api_call(self, params) | |||
|
630 | ||||
|
631 | result = [] | |||
|
632 | for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN): | |||
|
633 | result.append(repo.get_api_data()) | |||
|
634 | ret = jsonify(result) | |||
|
635 | ||||
|
636 | expected = ret | |||
|
637 | self._compare_ok(id_, expected, given=response.body) | |||
|
638 | ||||
481 | @parameterized.expand([('all', 'all'), |
|
639 | @parameterized.expand([('all', 'all'), | |
482 | ('dirs', 'dirs'), |
|
640 | ('dirs', 'dirs'), | |
483 | ('files', 'files'), ]) |
|
641 | ('files', 'files'), ]) | |
@@ -560,6 +718,56 b' class BaseTestApi(object):' | |||||
560 | expected = 'user `%s` does not exist' % owner |
|
718 | expected = 'user `%s` does not exist' % owner | |
561 | self._compare_error(id_, expected, given=response.body) |
|
719 | self._compare_error(id_, expected, given=response.body) | |
562 |
|
720 | |||
|
721 | def test_api_create_repo_dont_specify_owner(self): | |||
|
722 | repo_name = 'api-repo' | |||
|
723 | owner = 'i-dont-exist' | |||
|
724 | id_, params = _build_data(self.apikey, 'create_repo', | |||
|
725 | repo_name=repo_name, | |||
|
726 | repo_type='hg', | |||
|
727 | ) | |||
|
728 | response = api_call(self, params) | |||
|
729 | ||||
|
730 | repo = RepoModel().get_by_repo_name(repo_name) | |||
|
731 | ret = { | |||
|
732 | 'msg': 'Created new repository `%s`' % repo_name, | |||
|
733 | 'repo': jsonify(repo.get_api_data()) | |||
|
734 | } | |||
|
735 | expected = ret | |||
|
736 | self._compare_ok(id_, expected, given=response.body) | |||
|
737 | destroy_repo(repo_name) | |||
|
738 | ||||
|
739 | def test_api_create_repo_by_non_admin(self): | |||
|
740 | repo_name = 'api-repo' | |||
|
741 | owner = 'i-dont-exist' | |||
|
742 | id_, params = _build_data(self.apikey_regular, 'create_repo', | |||
|
743 | repo_name=repo_name, | |||
|
744 | repo_type='hg', | |||
|
745 | ) | |||
|
746 | response = api_call(self, params) | |||
|
747 | ||||
|
748 | repo = RepoModel().get_by_repo_name(repo_name) | |||
|
749 | ret = { | |||
|
750 | 'msg': 'Created new repository `%s`' % repo_name, | |||
|
751 | 'repo': jsonify(repo.get_api_data()) | |||
|
752 | } | |||
|
753 | expected = ret | |||
|
754 | self._compare_ok(id_, expected, given=response.body) | |||
|
755 | destroy_repo(repo_name) | |||
|
756 | ||||
|
757 | def test_api_create_repo_by_non_admin_specify_owner(self): | |||
|
758 | repo_name = 'api-repo' | |||
|
759 | owner = 'i-dont-exist' | |||
|
760 | id_, params = _build_data(self.apikey_regular, 'create_repo', | |||
|
761 | repo_name=repo_name, | |||
|
762 | repo_type='hg', | |||
|
763 | owner=owner | |||
|
764 | ) | |||
|
765 | response = api_call(self, params) | |||
|
766 | ||||
|
767 | expected = 'Only RhodeCode admin can specify `owner` param' | |||
|
768 | self._compare_error(id_, expected, given=response.body) | |||
|
769 | destroy_repo(repo_name) | |||
|
770 | ||||
563 | def test_api_create_repo_exists(self): |
|
771 | def test_api_create_repo_exists(self): | |
564 | repo_name = self.REPO |
|
772 | repo_name = self.REPO | |
565 | id_, params = _build_data(self.apikey, 'create_repo', |
|
773 | id_, params = _build_data(self.apikey, 'create_repo', | |
@@ -598,6 +806,35 b' class BaseTestApi(object):' | |||||
598 | expected = ret |
|
806 | expected = ret | |
599 | self._compare_ok(id_, expected, given=response.body) |
|
807 | self._compare_ok(id_, expected, given=response.body) | |
600 |
|
808 | |||
|
809 | def test_api_delete_repo_by_non_admin(self): | |||
|
810 | repo_name = 'api_delete_me' | |||
|
811 | create_repo(repo_name, self.REPO_TYPE, owner=self.TEST_USER_LOGIN) | |||
|
812 | try: | |||
|
813 | id_, params = _build_data(self.apikey_regular, 'delete_repo', | |||
|
814 | repoid=repo_name,) | |||
|
815 | response = api_call(self, params) | |||
|
816 | ||||
|
817 | ret = { | |||
|
818 | 'msg': 'Deleted repository `%s`' % repo_name, | |||
|
819 | 'success': True | |||
|
820 | } | |||
|
821 | expected = ret | |||
|
822 | self._compare_ok(id_, expected, given=response.body) | |||
|
823 | finally: | |||
|
824 | destroy_repo(repo_name) | |||
|
825 | ||||
|
826 | def test_api_delete_repo_by_non_admin_no_permission(self): | |||
|
827 | repo_name = 'api_delete_me' | |||
|
828 | create_repo(repo_name, self.REPO_TYPE) | |||
|
829 | try: | |||
|
830 | id_, params = _build_data(self.apikey_regular, 'delete_repo', | |||
|
831 | repoid=repo_name,) | |||
|
832 | response = api_call(self, params) | |||
|
833 | expected = 'repository `%s` does not exist' % (repo_name) | |||
|
834 | self._compare_error(id_, expected, given=response.body) | |||
|
835 | finally: | |||
|
836 | destroy_repo(repo_name) | |||
|
837 | ||||
601 | def test_api_delete_repo_exception_occurred(self): |
|
838 | def test_api_delete_repo_exception_occurred(self): | |
602 | repo_name = 'api_delete_me' |
|
839 | repo_name = 'api_delete_me' | |
603 | create_repo(repo_name, self.REPO_TYPE) |
|
840 | create_repo(repo_name, self.REPO_TYPE) | |
@@ -630,6 +867,49 b' class BaseTestApi(object):' | |||||
630 | self._compare_ok(id_, expected, given=response.body) |
|
867 | self._compare_ok(id_, expected, given=response.body) | |
631 | destroy_repo(fork_name) |
|
868 | destroy_repo(fork_name) | |
632 |
|
869 | |||
|
870 | def test_api_fork_repo_non_admin(self): | |||
|
871 | fork_name = 'api-repo-fork' | |||
|
872 | id_, params = _build_data(self.apikey_regular, 'fork_repo', | |||
|
873 | repoid=self.REPO, | |||
|
874 | fork_name=fork_name, | |||
|
875 | ) | |||
|
876 | response = api_call(self, params) | |||
|
877 | ||||
|
878 | ret = { | |||
|
879 | 'msg': 'Created fork of `%s` as `%s`' % (self.REPO, | |||
|
880 | fork_name), | |||
|
881 | 'success': True | |||
|
882 | } | |||
|
883 | expected = ret | |||
|
884 | self._compare_ok(id_, expected, given=response.body) | |||
|
885 | destroy_repo(fork_name) | |||
|
886 | ||||
|
887 | def test_api_fork_repo_non_admin_specify_owner(self): | |||
|
888 | fork_name = 'api-repo-fork' | |||
|
889 | id_, params = _build_data(self.apikey_regular, 'fork_repo', | |||
|
890 | repoid=self.REPO, | |||
|
891 | fork_name=fork_name, | |||
|
892 | owner=TEST_USER_ADMIN_LOGIN, | |||
|
893 | ) | |||
|
894 | response = api_call(self, params) | |||
|
895 | expected = 'Only RhodeCode admin can specify `owner` param' | |||
|
896 | self._compare_error(id_, expected, given=response.body) | |||
|
897 | destroy_repo(fork_name) | |||
|
898 | ||||
|
899 | def test_api_fork_repo_non_admin_no_permission_to_fork(self): | |||
|
900 | RepoModel().grant_user_permission(repo=self.REPO, | |||
|
901 | user=self.TEST_USER_LOGIN, | |||
|
902 | perm='repository.none') | |||
|
903 | fork_name = 'api-repo-fork' | |||
|
904 | id_, params = _build_data(self.apikey_regular, 'fork_repo', | |||
|
905 | repoid=self.REPO, | |||
|
906 | fork_name=fork_name, | |||
|
907 | ) | |||
|
908 | response = api_call(self, params) | |||
|
909 | expected = 'repository `%s` does not exist' % (self.REPO) | |||
|
910 | self._compare_error(id_, expected, given=response.body) | |||
|
911 | destroy_repo(fork_name) | |||
|
912 | ||||
633 | def test_api_fork_repo_unknown_owner(self): |
|
913 | def test_api_fork_repo_unknown_owner(self): | |
634 | fork_name = 'api-repo-fork' |
|
914 | fork_name = 'api-repo-fork' | |
635 | owner = 'i-dont-exist' |
|
915 | owner = 'i-dont-exist' |
@@ -82,6 +82,7 b' class TestNotificationsController(TestCo' | |||||
82 | response = self.app.delete(url('notification', |
|
82 | response = self.app.delete(url('notification', | |
83 | notification_id= |
|
83 | notification_id= | |
84 | notification.notification_id)) |
|
84 | notification.notification_id)) | |
|
85 | self.assertEqual(response.body, 'ok') | |||
85 |
|
86 | |||
86 | cur_user = User.get(cur_usr_id) |
|
87 | cur_user = User.get(cur_usr_id) | |
87 | self.assertEqual(cur_user.notifications, []) |
|
88 | self.assertEqual(cur_user.notifications, []) |
@@ -98,7 +98,7 b' class TestCompareController(TestControll' | |||||
98 | )) |
|
98 | )) | |
99 |
|
99 | |||
100 | try: |
|
100 | try: | |
101 |
response.mustcontain('%s@%s - |
|
101 | response.mustcontain('%s@%s -> %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2)) | |
102 | response.mustcontain("""Showing 2 commits""") |
|
102 | response.mustcontain("""Showing 2 commits""") | |
103 | response.mustcontain("""1 file changed with 2 insertions and 0 deletions""") |
|
103 | response.mustcontain("""1 file changed with 2 insertions and 0 deletions""") | |
104 |
|
104 | |||
@@ -156,7 +156,7 b' class TestCompareController(TestControll' | |||||
156 | )) |
|
156 | )) | |
157 |
|
157 | |||
158 | try: |
|
158 | try: | |
159 |
response.mustcontain('%s@%s - |
|
159 | response.mustcontain('%s@%s -> %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2)) | |
160 | response.mustcontain("""Showing 2 commits""") |
|
160 | response.mustcontain("""Showing 2 commits""") | |
161 | response.mustcontain("""1 file changed with 2 insertions and 0 deletions""") |
|
161 | response.mustcontain("""1 file changed with 2 insertions and 0 deletions""") | |
162 |
|
162 | |||
@@ -191,7 +191,7 b' class TestCompareController(TestControll' | |||||
191 | # )) |
|
191 | # )) | |
192 | # |
|
192 | # | |
193 | # try: |
|
193 | # try: | |
194 |
# response.mustcontain('%s@%s - |
|
194 | # response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2)) | |
195 | # ## outgoing changesets between those revisions |
|
195 | # ## outgoing changesets between those revisions | |
196 | # |
|
196 | # | |
197 | # response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO)) |
|
197 | # response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO)) | |
@@ -226,7 +226,7 b' class TestCompareController(TestControll' | |||||
226 | # )) |
|
226 | # )) | |
227 | # |
|
227 | # | |
228 | # try: |
|
228 | # try: | |
229 |
# response.mustcontain('%s@%s - |
|
229 | # response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2)) | |
230 | # ## outgoing changesets between those revisions |
|
230 | # ## outgoing changesets between those revisions | |
231 | # |
|
231 | # | |
232 | # response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO)) |
|
232 | # response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO)) | |
@@ -312,7 +312,7 b' class TestCompareController(TestControll' | |||||
312 | # )) |
|
312 | # )) | |
313 | # |
|
313 | # | |
314 | # try: |
|
314 | # try: | |
315 |
# #response.mustcontain('%s@%s - |
|
315 | # #response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2)) | |
316 | # |
|
316 | # | |
317 | # #add new commit into parent ! |
|
317 | # #add new commit into parent ! | |
318 | # cs0 = ScmModel().create_node( |
|
318 | # cs0 = ScmModel().create_node( | |
@@ -336,7 +336,7 b' class TestCompareController(TestControll' | |||||
336 | # bundle=False |
|
336 | # bundle=False | |
337 | # )) |
|
337 | # )) | |
338 | # |
|
338 | # | |
339 |
# response.mustcontain('%s@%s - |
|
339 | # response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2)) | |
340 | # response.mustcontain("""file1-line1-from-fork""") |
|
340 | # response.mustcontain("""file1-line1-from-fork""") | |
341 | # response.mustcontain("""file2-line1-from-fork""") |
|
341 | # response.mustcontain("""file2-line1-from-fork""") | |
342 | # response.mustcontain("""file3-line1-from-fork""") |
|
342 | # response.mustcontain("""file3-line1-from-fork""") |
@@ -19,7 +19,7 b' class TestCompareController(TestControll' | |||||
19 | other_ref_type="tag", |
|
19 | other_ref_type="tag", | |
20 | other_ref=tag2, |
|
20 | other_ref=tag2, | |
21 | )) |
|
21 | )) | |
22 |
response.mustcontain('%s@%s - |
|
22 | response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2)) | |
23 | ## outgoing changesets between tags |
|
23 | ## outgoing changesets between tags | |
24 | response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO) |
|
24 | response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO) | |
25 | response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO) |
|
25 | response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO) | |
@@ -56,7 +56,7 b' class TestCompareController(TestControll' | |||||
56 | other_ref=tag2, |
|
56 | other_ref=tag2, | |
57 | bundle=False |
|
57 | bundle=False | |
58 | )) |
|
58 | )) | |
59 |
response.mustcontain('%s@%s - |
|
59 | response.mustcontain('%s@%s -> %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2)) | |
60 |
|
60 | |||
61 | ## outgoing changesets between tags |
|
61 | ## outgoing changesets between tags | |
62 | response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO) |
|
62 | response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO) | |
@@ -92,7 +92,7 b' class TestCompareController(TestControll' | |||||
92 | other_ref='default', |
|
92 | other_ref='default', | |
93 | )) |
|
93 | )) | |
94 |
|
94 | |||
95 |
response.mustcontain('%s@default - |
|
95 | response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO)) | |
96 | # branch are equal |
|
96 | # branch are equal | |
97 | response.mustcontain('<span class="empty_data">No files</span>') |
|
97 | response.mustcontain('<span class="empty_data">No files</span>') | |
98 | response.mustcontain('<span class="empty_data">No changesets</span>') |
|
98 | response.mustcontain('<span class="empty_data">No changesets</span>') | |
@@ -107,7 +107,7 b' class TestCompareController(TestControll' | |||||
107 | other_ref='master', |
|
107 | other_ref='master', | |
108 | )) |
|
108 | )) | |
109 |
|
109 | |||
110 |
response.mustcontain('%s@master - |
|
110 | response.mustcontain('%s@master -> %s@master' % (GIT_REPO, GIT_REPO)) | |
111 | # branch are equal |
|
111 | # branch are equal | |
112 | response.mustcontain('<span class="empty_data">No files</span>') |
|
112 | response.mustcontain('<span class="empty_data">No files</span>') | |
113 | response.mustcontain('<span class="empty_data">No changesets</span>') |
|
113 | response.mustcontain('<span class="empty_data">No changesets</span>') | |
@@ -124,7 +124,7 b' class TestCompareController(TestControll' | |||||
124 | other_ref_type="rev", |
|
124 | other_ref_type="rev", | |
125 | other_ref=rev2, |
|
125 | other_ref=rev2, | |
126 | )) |
|
126 | )) | |
127 |
response.mustcontain('%s@%s - |
|
127 | response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2)) | |
128 | ## outgoing changesets between those revisions |
|
128 | ## outgoing changesets between those revisions | |
129 | response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2)) |
|
129 | response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2)) | |
130 |
|
130 | |||
@@ -144,7 +144,7 b' class TestCompareController(TestControll' | |||||
144 | other_ref_type="rev", |
|
144 | other_ref_type="rev", | |
145 | other_ref=rev2, |
|
145 | other_ref=rev2, | |
146 | )) |
|
146 | )) | |
147 |
response.mustcontain('%s@%s - |
|
147 | response.mustcontain('%s@%s -> %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2)) | |
148 | ## outgoing changesets between those revisions |
|
148 | ## outgoing changesets between those revisions | |
149 | response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12])) |
|
149 | response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12])) | |
150 | response.mustcontain('1 file changed with 7 insertions and 0 deletions') |
|
150 | response.mustcontain('1 file changed with 7 insertions and 0 deletions') |
@@ -3,6 +3,9 b' from rhodecode.tests import *' | |||||
3 | from rhodecode.model.meta import Session |
|
3 | from rhodecode.model.meta import Session | |
4 | from rhodecode.model.db import User, RhodeCodeSetting, Repository |
|
4 | from rhodecode.model.db import User, RhodeCodeSetting, Repository | |
5 | from rhodecode.lib.utils import set_rhodecode_config |
|
5 | from rhodecode.lib.utils import set_rhodecode_config | |
|
6 | from rhodecode.tests.models.common import _make_repo, _make_group | |||
|
7 | from rhodecode.model.repo import RepoModel | |||
|
8 | from rhodecode.model.repos_group import ReposGroupModel | |||
6 |
|
9 | |||
7 |
|
10 | |||
8 | class TestHomeController(TestController): |
|
11 | class TestHomeController(TestController): | |
@@ -61,18 +64,45 b' merge" class="tooltip" href="/vcs_test_h' | |||||
61 | Session().add(anon) |
|
64 | Session().add(anon) | |
62 | Session().commit() |
|
65 | Session().commit() | |
63 |
|
66 | |||
|
67 | def _set_l_dash(self, set_to): | |||
|
68 | self.app.post(url('admin_setting', setting_id='visual'), | |||
|
69 | params=dict(_method='put', | |||
|
70 | rhodecode_lightweight_dashboard=set_to,)) | |||
|
71 | ||||
64 | def test_index_with_lightweight_dashboard(self): |
|
72 | def test_index_with_lightweight_dashboard(self): | |
65 | self.log_user() |
|
73 | self.log_user() | |
66 |
|
74 | self._set_l_dash(True) | ||
67 | def set_l_dash(set_to): |
|
|||
68 | self.app.post(url('admin_setting', setting_id='visual'), |
|
|||
69 | params=dict(_method='put', |
|
|||
70 | rhodecode_lightweight_dashboard=set_to,)) |
|
|||
71 |
|
||||
72 | set_l_dash(True) |
|
|||
73 |
|
75 | |||
74 | try: |
|
76 | try: | |
75 | response = self.app.get(url(controller='home', action='index')) |
|
77 | response = self.app.get(url(controller='home', action='index')) | |
76 | response.mustcontain("""var data = {"totalRecords": %s""" % len(Repository.getAll())) |
|
78 | response.mustcontain("""var data = {"totalRecords": %s""" % len(Repository.getAll())) | |
77 | finally: |
|
79 | finally: | |
78 | set_l_dash(False) |
|
80 | self._set_l_dash(False) | |
|
81 | ||||
|
82 | def test_index_page_on_groups(self): | |||
|
83 | self.log_user() | |||
|
84 | _make_repo(name='gr1/repo_in_group', repos_group=_make_group('gr1')) | |||
|
85 | Session().commit() | |||
|
86 | response = self.app.get(url('repos_group_home', group_name='gr1')) | |||
|
87 | ||||
|
88 | try: | |||
|
89 | response.mustcontain("""gr1/repo_in_group""") | |||
|
90 | finally: | |||
|
91 | RepoModel().delete('gr1/repo_in_group') | |||
|
92 | ReposGroupModel().delete(repos_group='gr1', force_delete=True) | |||
|
93 | Session().commit() | |||
|
94 | ||||
|
95 | def test_index_page_on_groups_with_lightweight_dashboard(self): | |||
|
96 | self.log_user() | |||
|
97 | self._set_l_dash(True) | |||
|
98 | _make_repo(name='gr1/repo_in_group', repos_group=_make_group('gr1')) | |||
|
99 | Session().commit() | |||
|
100 | response = self.app.get(url('repos_group_home', group_name='gr1')) | |||
|
101 | ||||
|
102 | try: | |||
|
103 | response.mustcontain("""gr1/repo_in_group""") | |||
|
104 | finally: | |||
|
105 | self._set_l_dash(False) | |||
|
106 | RepoModel().delete('gr1/repo_in_group') | |||
|
107 | ReposGroupModel().delete(repos_group='gr1', force_delete=True) | |||
|
108 | Session().commit() |
@@ -10,10 +10,7 b' class TestJournalController(TestControll' | |||||
10 | self.log_user() |
|
10 | self.log_user() | |
11 | response = self.app.get(url(controller='journal', action='index')) |
|
11 | response = self.app.get(url(controller='journal', action='index')) | |
12 |
|
12 | |||
13 | # Test response... |
|
13 | response.mustcontain("""<div class="journal_day">%s</div>""" % datetime.date.today()) | |
14 | assert """ <span id="follow_toggle_1" class="following" title="Stop following this repository""" in response.body, 'no info about stop follwoing repo id 1' |
|
|||
15 |
|
||||
16 | assert """<div class="journal_day">%s</div>""" % datetime.date.today() in response.body, 'no info about action journal day' |
|
|||
17 |
|
14 | |||
18 | def test_stop_following_repository(self): |
|
15 | def test_stop_following_repository(self): | |
19 | session = self.log_user() |
|
16 | session = self.log_user() |
@@ -1,1 +1,1 b'' | |||||
1 | #TODO; write tests when we activate algo for permissions. No newline at end of file |
|
1 | #TODO; write tests when we activate algo for permissions. |
@@ -39,6 +39,7 b' from rhodecode.tests import *' | |||||
39 | from rhodecode.model.db import User, Repository, UserLog |
|
39 | from rhodecode.model.db import User, Repository, UserLog | |
40 | from rhodecode.model.meta import Session |
|
40 | from rhodecode.model.meta import Session | |
41 | from rhodecode.model.repo import RepoModel |
|
41 | from rhodecode.model.repo import RepoModel | |
|
42 | from rhodecode.model.user import UserModel | |||
42 |
|
43 | |||
43 | DEBUG = True |
|
44 | DEBUG = True | |
44 | HOST = '127.0.0.1:5000' # test host |
|
45 | HOST = '127.0.0.1:5000' # test host | |
@@ -420,3 +421,41 b' class TestVCSOperations(unittest.TestCas' | |||||
420 | # Session.remove() |
|
421 | # Session.remove() | |
421 | # r = Repository.get_by_repo_name(GIT_REPO) |
|
422 | # r = Repository.get_by_repo_name(GIT_REPO) | |
422 | # assert r.locked == [None, None] |
|
423 | # assert r.locked == [None, None] | |
|
424 | ||||
|
425 | def test_ip_restriction_hg(self): | |||
|
426 | user_model = UserModel() | |||
|
427 | new_ip = user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32') | |||
|
428 | Session().commit() | |||
|
429 | clone_url = _construct_url(HG_REPO) | |||
|
430 | stdout, stderr = Command('/tmp').execute('hg clone', clone_url) | |||
|
431 | assert 'abort: HTTP Error 403: Forbidden' in stderr | |||
|
432 | ||||
|
433 | #release IP restrictions | |||
|
434 | clone_url = _construct_url(HG_REPO) | |||
|
435 | user_model.delete_extra_ip(TEST_USER_ADMIN_LOGIN, new_ip.ip_id) | |||
|
436 | Session().commit() | |||
|
437 | stdout, stderr = Command('/tmp').execute('hg clone', clone_url) | |||
|
438 | ||||
|
439 | assert 'requesting all changes' in stdout | |||
|
440 | assert 'adding changesets' in stdout | |||
|
441 | assert 'adding manifests' in stdout | |||
|
442 | assert 'adding file changes' in stdout | |||
|
443 | ||||
|
444 | assert stderr == '' | |||
|
445 | ||||
|
446 | def test_ip_restriction_git(self): | |||
|
447 | user_model = UserModel() | |||
|
448 | new_ip = user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32') | |||
|
449 | Session().commit() | |||
|
450 | clone_url = _construct_url(GIT_REPO) | |||
|
451 | stdout, stderr = Command('/tmp').execute('git clone', clone_url) | |||
|
452 | assert 'error: The requested URL returned error: 403 Forbidden' in stderr | |||
|
453 | ||||
|
454 | #release IP restrictions | |||
|
455 | clone_url = _construct_url(GIT_REPO) | |||
|
456 | user_model.delete_extra_ip(TEST_USER_ADMIN_LOGIN, new_ip.ip_id) | |||
|
457 | Session().commit() | |||
|
458 | stdout, stderr = Command('/tmp').execute('git clone', clone_url) | |||
|
459 | ||||
|
460 | assert 'Cloning into' in stdout | |||
|
461 | assert stderr == '' |
@@ -123,15 +123,16 b' class TestLibs(unittest.TestCase):' | |||||
123 | from rhodecode.lib.utils2 import age |
|
123 | from rhodecode.lib.utils2 import age | |
124 | n = datetime.datetime.now() |
|
124 | n = datetime.datetime.now() | |
125 | delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs) |
|
125 | delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs) | |
|
126 | prev_month = n.month - 1 if n.month != 1 else n.month - 2 | |||
126 | self.assertEqual(age(n), u'just now') |
|
127 | self.assertEqual(age(n), u'just now') | |
127 | self.assertEqual(age(n - delt(seconds=1)), u'1 second ago') |
|
128 | self.assertEqual(age(n - delt(seconds=1)), u'1 second ago') | |
128 | self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago') |
|
129 | self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago') | |
129 | self.assertEqual(age(n - delt(hours=1)), u'1 hour ago') |
|
130 | self.assertEqual(age(n - delt(hours=1)), u'1 hour ago') | |
130 | self.assertEqual(age(n - delt(hours=24)), u'1 day ago') |
|
131 | self.assertEqual(age(n - delt(hours=24)), u'1 day ago') | |
131 | self.assertEqual(age(n - delt(hours=24 * 5)), u'5 days ago') |
|
132 | self.assertEqual(age(n - delt(hours=24 * 5)), u'5 days ago') | |
132 |
self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[ |
|
133 | self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[prev_month]))), | |
133 | u'1 month ago') |
|
134 | u'1 month ago') | |
134 |
self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[ |
|
135 | self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[prev_month] + 2))), | |
135 | u'1 month and 2 days ago') |
|
136 | u'1 month and 2 days ago') | |
136 | self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago') |
|
137 | self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago') | |
137 |
|
138 |
@@ -60,10 +60,10 b' if sys.version_info < (2, 7):' | |||||
60 | requirements.append("unittest2") |
|
60 | requirements.append("unittest2") | |
61 |
|
61 | |||
62 | if is_windows: |
|
62 | if is_windows: | |
63 |
requirements.append("mercurial==2.4. |
|
63 | requirements.append("mercurial==2.4.2") | |
64 | else: |
|
64 | else: | |
65 | requirements.append("py-bcrypt") |
|
65 | requirements.append("py-bcrypt") | |
66 |
requirements.append("mercurial==2.4. |
|
66 | requirements.append("mercurial==2.4.2") | |
67 |
|
67 | |||
68 |
|
68 | |||
69 | dependency_links = [ |
|
69 | dependency_links = [ |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now