##// END OF EJS Templates
merged with beta
marcink -
r3179:cd50d1b5 merge default
parent child Browse files
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 "admin" :  "<bool>",
245 "active" : "<bool>",
211 "ldap_dn" : "<ldap_dn>",
246 "admin" :  "<bool>",
212 "last_login": "<last_login>",
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 "admin" :  "<bool>",
287 "active" : "<bool>",
252 "ldap_dn" : "<ldap_dn>",
288 "admin" :  "<bool>",
253 "last_login": "<last_login>",
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 be executed only using api_key belonging to user with admin rights.
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 "private": : "<bool>",
600 "enable_downloads": "<bool>",
563 "created_on" : "<datetimecreated>",
601 "enable_locking": "<bool>",
564 "description" : "<description>",
602 "enable_statistics": "<bool>",
565 "landing_rev": "<landing_rev>",
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 api_key
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": "<repo_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, 1)
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__ = 9 # defines current db version for migrations
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 == c.rhodecode_user.user_id)
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 == c.rhodecode_user.user_id)
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 == c.user.user_id)
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([x[0] for x in self.repo_perms_choices],
116 _form = DefaultPermissionsForm(
112 [x[0] for x in self.group_perms_choices],
117 [x[0] for x in self.repo_perms_choices],
113 [x[0] for x in self.register_choices],
118 [x[0] for x in self.group_perms_choices],
114 [x[0] for x in self.create_choices],
119 [x[0] for x in self.register_choices],
115 [x[0] for x in self.fork_choices])()
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 = {'_method': 'put',
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=True,
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.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
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 ' rescanned added: %s,removed: %s') % (added, removed),
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 [test_email] else None
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 HasPermissionAllDecorator, \
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 get_user(self, apiuser, userid):
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 @HasPermissionAnyDecorator('hg.admin')
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, repo_type,
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 withina group name the groups will be created
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 @HasPermissionAnyDecorator('hg.admin')
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 @HasPermissionAnyDecorator('hg.admin')
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 @HasPermissionAnyDecorator('hg.admin')
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 @HasPermissionAnyDecorator('hg.admin')
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 .get_status(c.rhodecode_db_repo.repo_id,
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 = lambda: co.author.user_id == c.rhodecode_user.user_id
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 or c.other_repo is None:
106 if c.org_repo is None:
107 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
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 if self.rhodecode_user.is_authenticated \
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 = c.repo_name
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 = lambda: co.author.user_id == c.rhodecode_user.user_id
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: 2012-10-27 15:06+0900\n"
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 "LDAP設定を更新しました"
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 "ユーザー %s の更新中にエラーが発生しました"
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 #, fuzzy, python-format
958 #, python-format
963 msgid "in %s and %s"
959 msgid "in %s and %s"
964 msgstr "%s と %s 前"
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 "APIキー"
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システムからアクセスされないような名前に、特別な方法で変更されます。\n"
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 #, fuzzy, python-format
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 "%s とフォークを比較"
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 #, fuzzy, python-format
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 "rhodecodeへの登録を受け付けました"
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 "%d レビュアー"
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 #, fuzzy, python-format
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: 2012-11-25 03:42+0200\n"
12 "PO-Revision-Date: 2013-01-18 18:12+0100\n"
13 "Last-Translator: Nemo <areczek01@gmail.com>\n"
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 rhodecode/controllers/changeset.py:97
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 rhodecode/controllers/files.py:325
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 rhodecode/controllers/files.py:372
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 rhodecode/controllers/files.py:378
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 rhodecode/controllers/pullrequests.py:74
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 rhodecode/controllers/pullrequests.py:78
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 rhodecode/controllers/journal.py:261
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 rhodecode/controllers/journal.py:265
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 rhodecode/model/scm.py:556
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 "Ustawienia LDAP zostały zaktualizowane"
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 użytkownika %s"
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 rhodecode/model/validators.py:300
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 rhodecode/lib/helpers.py:676
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 rhodecode/lib/helpers.py:678
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 rhodecode/lib/helpers.py:680
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 rhodecode/model/db.py:1183
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 rhodecode/model/db.py:1184
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 rhodecode/model/db.py:1185
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 rhodecode/model/db.py:1186
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 rhodecode/model/db.py:1188
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 rhodecode/model/db.py:1189
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 rhodecode/model/db.py:1190
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 rhodecode/model/db.py:1191
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 rhodecode/model/db.py:1193
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 rhodecode/model/db.py:1194
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 rhodecode/model/db.py:1195
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 rhodecode/model/db.py:1196
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 rhodecode/model/db.py:1197
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 rhodecode/model/db.py:1198
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 rhodecode/model/db.py:1199
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 rhodecode/model/db.py:1202
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 rhodecode/model/db.py:1643
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 rhodecode/model/db.py:1644
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 rhodecode/model/db.py:1645
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 rhodecode/model/db.py:1646
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 rhodecode/model/user.py:281
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 rhodecode/templates/login.html:54
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 rhodecode/templates/register.html:20
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 rhodecode/templates/register.html:29
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 rhodecode/templates/base/base.html:103
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 rhodecode/templates/register.html:74
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 filtr..."
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 "pliki"
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 "grupy w repozytorium"
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 "domyślne"
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 API"
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 rhodecode/templates/forks/forks.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 #, fuzzy, python-format
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] "Potwierdz aby usunąć grupę %s wraz z %s repozytorium"
2368 msgstr[0] "Potwierdź żeby usunąć grupę %s wraz z %s repozytorium"
2411 msgstr[1] "Potwierdz aby usunąć grupę %s wraz z %s repozytoriami"
2369 msgstr[1] "Potwierdź żeby usunąć grupę %s wraz z %s repozytoriami"
2412 msgstr[2] "Potwierdz aby usunąć grupę %s wraz z %s repozytoriami"
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 "włącz"
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 rhodecode/templates/base/root.html:47
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 rhodecode/templates/base/root.html:50
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 fork w rodzicem"
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 #, fuzzy, python-format
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ów zmienionych"
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 #, fuzzy, python-format
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 inserjcami i %s usunieciami"
3299 msgstr[0] "%s plik został zmieniony z %s inercjami i %s usunięciami"
3374 msgstr[1] "%s plików zmienionych z %s inserjcami i %s usunieciami"
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 inserjcami i %s usunieciami"
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 "pokaż online komentarz"
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 #, fuzzy, python-format
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] "Wyswietlane %s commit"
3391 msgstr[0] "Pokaż %s komentarz"
3469 msgstr[1] "Wyswietlane %s commits"
3392 msgstr[1] "Pokaż %s komentarze"
3470 msgstr[2] "Wyswietlane %s commits"
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 "pliki"
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 "zmień status"
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 "Tytuł"
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 #, fuzzy, python-format
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 "zmień status"
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 "Udało Ci się zarejestrować na stronie"
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 "%d recenzent"
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 "[skasowane] repozytorium"
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 "usuń"
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 "porównanie"
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 04:19+0100\n"
11 "POT-Creation-Date: 2012-12-14 12:53+0800\n"
12 "PO-Revision-Date: 2012-11-26 15:19+0800\n"
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 "not allowed"
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 "LDAP设置已经成功更新"
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 "更新用户%s时发生错误"
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 "the filesystem please run the application again in order to rescan "
905 "filesystem please run the application again in order to rescan repositories"
904 "repositories"
906 msgstr ""
905 msgstr "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启RhodeCode以重新扫描版本库"
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 "owners or remove those repositories. %s"
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 " dashes and must begin with alphanumeric character"
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 "of the attribute that is equivalent to \"username\""
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 "be lost"
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 " permission, note that all custom default permission on repositories "
1885 "permission, note that all custom default permission on repositories group "
1884 "group will be lost"
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 " will be cached again"
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 "public journal"
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 "unaccesible for RhodeCode and VCS systems. If you need fully delete it "
2172 "for RhodeCode and VCS systems. If you need fully delete it from file system "
2169 "from file system please do it manually"
2173 "please do it manually"
2170 msgstr ""
2174 msgstr ""
2171 "这个版本库将以特殊的方式重命名这样RhodeCode和版本控制系统将不能访问它。\n"
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 rhodecode/templates/forks/forks.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 " groups and repositories inside"
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 "in the database check this option to scan obsolete data in database and "
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 "if `destroy` flag is checked "
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 "will return HTTP Error 406: Not Acceptable"
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 "take effect. Click this label to unlock."
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 "restart, and rescan is required"
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 rhodecode/templates/base/root.html:47
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 rhodecode/templates/base/root.html:50
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 #, fuzzy, python-format
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 "成功注册到RhodeCode"
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 "%d个检视者"
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 log.debug('Received username %s from container' % username)
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_key2)
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 to version %s' \
167 notify('attempting to do database upgrade from '
168 % __dbversion__)
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:%s', ui_.ui_section,
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:%s', ui_.ui_section,
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_4_0
3 rhodecode.model.db_1_5_0
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode <=1.5.X
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 = ' &raquo; '
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 new_lineno_class='lineno old', old_lineno_class='lineno new',
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 =&gt; \\1 </div>', value)
464 '<div class="metatag" tag="see">see =&gt; \\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 =&gt; <a href="/\\2">\\2</a></div>', value)
468 '<div class="metatag" tag="\\1">\\1 =&gt; <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', 'tables'])
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"' % (action, repo_name))
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"' % (action, repo_name))
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_change = repo.scm_instance.last_change
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:%s' % (section, k, v))
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:%s', ui_.ui_section,
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=>`%s`"
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 == 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_last_change(self, last_change=None):
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)(), v.UniqueList(not_empty=True))
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 other_ref, revisions, reviewers, title, description=None):
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.exception('Error removing repos_group %s' % repos_group)
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.url_sep().join(name.split(os.sep))
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,.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/folder_edit.png") no-repeat scroll 3px;
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', yt._tooltip);
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 ${h.submit('set',_('set'),class_="ui-btn large")}
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: 15,
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 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
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" class="table" style="display:none">
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"></div>
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: '&gt;',
199 };
227 previousPageLinkLabel: '&lt;',
200 var trans_defs = {
228 firstPageLinkLabel: '&lt;&lt;',
201 sortedBy:{key:"name",dir:"asc"},
229 lastPageLinkLabel: '&gt;&gt;',
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_LOADING:"${_('Loading...')}",
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 += '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
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;">&rsaquo;</span></div>
19 <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</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)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)}
5 ${c.repo_name} ${_('Compare')} ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -&gt; ${'%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)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} <a href="${c.swap_url}">[swap]</a>
31 ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -&gt; ${'%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, admin=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: '&lt;',
159 previousPageLinkLabel: '&lt;',
163 firstPageLinkLabel: '&lt;&lt;',
160 firstPageLinkLabel: '&lt;&lt;',
164 lastPageLinkLabel: '&gt;&gt;',
161 lastPageLinkLabel: '&gt;&gt;',
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 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
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.get('q_filter').value = '';
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: '&gt;',
219 previousPageLinkLabel: '&lt;',
220 firstPageLinkLabel: '&lt;&lt;',
221 lastPageLinkLabel: '&gt;&gt;',
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: '&gt;',
323 previousPageLinkLabel: '&lt;',
324 firstPageLinkLabel: '&lt;&lt;',
325 lastPageLinkLabel: '&gt;&gt;',
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.TEST_USER = UserModel().create_or_update(
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.TEST_USER.username
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_user')
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 `userid` arg in JSON DATA'
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 -> %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2))
101 response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (repo2.repo_name, rev1, repo1.repo_name, rev2))
159 response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
194 # response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
229 # response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (r2_name, rev1, r1_name, rev2))
315 # #response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (r2_name, rev1, r1_name, rev2))
339 # response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
22 response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (GIT_REPO, tag1, GIT_REPO, tag2))
59 response.mustcontain('%s@%s -&gt; %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 -> %s@default' % (HG_REPO, HG_REPO))
95 response.mustcontain('%s@default -&gt; %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 -> %s@master' % (GIT_REPO, GIT_REPO))
110 response.mustcontain('%s@master -&gt; %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 -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
127 response.mustcontain('%s@%s -&gt; %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 -> %s@%s' % (GIT_REPO, rev1, GIT_REPO, rev2))
147 response.mustcontain('%s@%s -&gt; %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[n.month - 1]))),
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[n.month - 1] + 2))),
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.1")
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.1")
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